Skip to content

Commit af68bf9

Browse files
committed
melange/lookingGlass: better object inspection
Reworks object parsing code so that we can inspect more types, present the information more clearly, and prevents misbehavior with types we shouldn't iterate like importers and other special objects. Notable behavior changes: - Boxed instance proxies such as Clutter.Color, Clutter.Rect, etc are now inspectable like other objects. - Inspecting or calling Eval() on non-iterable types no longer freezes up or shows warnings. - Errors during command eval() are now inspectable.
1 parent aa431c2 commit af68bf9

File tree

2 files changed

+139
-59
lines changed

2 files changed

+139
-59
lines changed

files/usr/share/cinnamon/cinnamon-looking-glass/page_inspect.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
import pageutils
44
from gi.repository import Gtk
55

6+
# keep in sync with lookingGlass.js
7+
NON_INSPECTABLE_TYPES = [
8+
"null",
9+
"undefined",
10+
"importer",
11+
"gimporter"
12+
]
13+
614
class InspectView(pageutils.BaseListView):
715
def __init__(self, parent):
816
self.parent = parent
@@ -124,7 +132,7 @@ def pushInspectionElement(self):
124132
self.insert.set_sensitive(True)
125133

126134
def updateInspector(self, path, objType, name, value, pushToStack=False):
127-
if objType == "object":
135+
if objType not in NON_INSPECTABLE_TYPES:
128136
if pushToStack:
129137
self.pushInspectionElement()
130138

js/ui/lookingGlass.js

Lines changed: 130 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const Clutter = imports.gi.Clutter;
55
const Cogl = imports.gi.Cogl;
66
const Gio = imports.gi.Gio;
77
const GLib = imports.gi.GLib;
8+
const GObject = imports.gi.GObject;
89
const Lang = imports.lang;
910
const Meta = imports.gi.Meta;
1011
const Signals = imports.signals;
@@ -36,13 +37,126 @@ var commandHeader = 'const Clutter = imports.gi.Clutter; ' +
3637
'const r = Lang.bind(Main.lookingGlass, Main.lookingGlass.getResult); ';
3738

3839
const HISTORY_KEY = 'looking-glass-history';
39-
function objectToString(o) {
40-
if (typeof(o) == typeof(objectToString)) {
41-
// special case this since the default is way, way too verbose
42-
return '<js function>';
43-
} else {
44-
return '' + o;
40+
41+
/* looking glass types:
42+
*
43+
* We use a couple of custom type strings in addition to normal js type strings(from typeof):
44+
* -"array": for objects which pass Array.isArray() and should only show indexed properties
45+
* -"delegate": objects with an 'actor' property pointing to an st/clutter actor
46+
* -"importer": importer objects like "imports" which shouldn't be probed
47+
* -"gimporter": "imports.gi" object
48+
* -"boxed": structs and other non-gobject c data types
49+
* -"gobject": gobjects
50+
* -"unknown": unidentifiable c object
51+
*/
52+
53+
// types with no properties or import files on get()
54+
const NON_INSPECTABLE_TYPES = [
55+
"null",
56+
"undefined",
57+
"importer",
58+
"gimporter"
59+
];
60+
61+
// not in use
62+
const IGNORE_PROPERTIES = [
63+
"toString",
64+
"constructor"
65+
];
66+
67+
// returns [typeString, valueString] for any object
68+
function getObjInfo(o) {
69+
let type, value;
70+
if (o === null) {
71+
type = "null";
72+
value = "[" + type + "]";
73+
} else if (o === undefined) {
74+
type = "undefined";
75+
value = "[" + type + "]";
76+
}
77+
78+
if (!value) {
79+
try {
80+
value = o.toString();
81+
} catch (e) {
82+
if (e.message.includes("not an object instance - cannot convert to GObject*")) {
83+
// work around Clutter.Actor.prototype.toString override not handling being
84+
// called with the prototype as 'this'
85+
value = GObject.Object.prototype.toString.call(o);
86+
} else {
87+
value = "[error getting value]";
88+
}
89+
}
90+
if (value.startsWith("[object Object delegate for")) {
91+
type = "delegate";
92+
} else if (value.includes("CinnamonGenericContainer")) {
93+
type = "gobject";
94+
} else if (value.includes("GIName:")) {
95+
try {
96+
type = value.match(/\[(.+) (?:instance proxy|prototype of) GIName:.+\]/)[1];
97+
if (type === "object")
98+
type = "gobject";
99+
} catch (e) {
100+
type = "unknown";
101+
}
102+
} else if (value.startsWith("[GjsFileImporter")) {
103+
type = "importer";
104+
} else if (value.includes("GjsModule")) {
105+
if (value === "[object GjsModule gi]")
106+
type = "gimporter";
107+
else
108+
type = "module";
109+
} else if (Array.isArray(o)) {
110+
type = "array";
111+
} else {
112+
type = typeof(o);
113+
}
45114
}
115+
116+
if (value === "")
117+
value = "[empty]";
118+
119+
return [type, value];
120+
}
121+
122+
function getObjKeysInfo(obj) {
123+
let [type, ] = getObjInfo(obj);
124+
if (NON_INSPECTABLE_TYPES.includes(type))
125+
return [];
126+
127+
let keys = new Set();
128+
let curProto = obj;
129+
130+
// we ignore the Object prototype for everything except basic objects,
131+
// to skip the common object properties unless we can't avoid it
132+
while (curProto && (type === "object" || curProto !== Object.prototype)) {
133+
let ownKeys;
134+
if (type === "array") {
135+
ownKeys = curProto.keys(); // index properties only
136+
} else {
137+
ownKeys = Reflect.ownKeys(curProto); // all own properties and symbols
138+
}
139+
140+
for (let key of ownKeys)
141+
keys.add(key);
142+
143+
curProto = Object.getPrototypeOf(curProto);
144+
}
145+
146+
return Array.from(keys).map((k) => {
147+
let [t, v] = getObjInfo(obj[k]);
148+
return { name: k.toString(), type: t, value: v, shortValue: v.split(`\n`)[0] }
149+
});
150+
}
151+
152+
function tryEval(js) {
153+
let out;
154+
try {
155+
out = eval(js);
156+
} catch (e) {
157+
out = e;
158+
}
159+
return out;
46160
}
47161

48162
function WindowList() {
@@ -93,13 +207,13 @@ WindowList.prototype = {
93207

94208
let lgInfo = {
95209
id: metaWindow._lgId.toString(),
96-
title: objectToString(metaWindow.title),
97-
wmclass: objectToString(metaWindow.get_wm_class()),
210+
title: metaWindow.title + '',
211+
wmclass: metaWindow.get_wm_class() + '',
98212
app: '' };
99213

100214
let app = tracker.get_window_app(metaWindow);
101215
if (app != null && !app.is_window_backed()) {
102-
lgInfo.app = objectToString(app.get_id());
216+
lgInfo.app = app.get_id() + '';
103217
} else {
104218
lgInfo.app = '<untracked>';
105219
}
@@ -454,43 +568,18 @@ Melange.prototype = {
454568
_pushResult: function(command, obj, tooltip) {
455569
let index = this._results.length;
456570
let result = {"o": obj, "index": index};
457-
this.rawResults.push({command: command, type: typeof(obj), object: objectToString(obj), index: index.toString(), tooltip: tooltip});
571+
let [type, value] = getObjInfo(obj);
572+
this.rawResults.push({command: command, type: type, object: value, index: index.toString(), tooltip: tooltip});
458573
this.emitResultUpdate();
459-
574+
460575
this._results.push(result);
461576
this._it = obj;
462577
},
463578

464579
inspect: function(path) {
465580
let fullCmd = commandHeader + path;
466-
467-
let result = eval(fullCmd);
468-
let resultObj = [];
469-
for (let key in result) {
470-
let type = typeof(result[key]);
471-
472-
//fixme: move this shortvalue stuff to python lg
473-
let shortValue, value;
474-
if (type === "undefined") {
475-
value = "";
476-
shortValue = "";
477-
} else if (result[key] === null) {
478-
value = "[null]";
479-
shortValue = value;
480-
} else {
481-
value = result[key].toString();
482-
shortValue = value;
483-
let i = value.indexOf('\n');
484-
let j = value.indexOf('\r');
485-
if (j != -1 && (i == -1 || i > j))
486-
i = j;
487-
if (i != -1)
488-
shortValue = value.substr(0, i) + '.. <more>';
489-
}
490-
resultObj.push({ name: key, type: type, value: value, shortValue: shortValue});
491-
}
492-
493-
return resultObj;
581+
let result = tryEval(fullCmd);
582+
return getObjKeysInfo(result);
494583
},
495584

496585
getIt: function () {
@@ -519,24 +608,14 @@ Melange.prototype = {
519608
this._history.addItem(command);
520609

521610
let fullCmd = commandHeader + command;
522-
523-
let resultObj;
524-
525611
let ts = GLib.get_monotonic_time();
526612

527-
try {
528-
resultObj = eval(fullCmd);
529-
} catch (e) {
530-
resultObj = '<exception ' + e + '>';
531-
}
613+
let resultObj = tryEval(fullCmd);
532614

533615
let ts2 = GLib.get_monotonic_time();
534-
535616
let tooltip = _("Execution time (ms): ") + (ts2 - ts) / 1000;
536617

537618
this._pushResult(command, resultObj, tooltip);
538-
539-
return;
540619
},
541620

542621
// DBus function
@@ -547,14 +626,7 @@ Melange.prototype = {
547626
// DBus function
548627
AddResult: function(path) {
549628
let fullCmd = commandHeader + path;
550-
551-
let resultObj;
552-
try {
553-
resultObj = eval(fullCmd);
554-
} catch (e) {
555-
resultObj = '<exception ' + e + '>';
556-
}
557-
this._pushResult(path, resultObj, "");
629+
this._pushResult(path, tryEval(fullCmd), "");
558630
},
559631

560632
// DBus function

0 commit comments

Comments
 (0)