mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] devtools: observe variables
This commit allows the user to observe variables so that their content will be refreshed every 200ms and will be displayed as long as they are found in the tree.
This commit is contained in:
committed by
Géry Debongnie
parent
ccd31f12d9
commit
7078d64049
+11
@@ -20,6 +20,17 @@
|
|||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
<div class="details-container">
|
<div class="details-container">
|
||||||
|
<div t-if="store.observedVariables.length and store.observedVariables.some((v) => v.visible)" id="observedVariables" class="details-panel ps-2 py-1">
|
||||||
|
<div class="d-flex mb-2">
|
||||||
|
<div class="w-100">
|
||||||
|
<b class="ps-2">observed variables</b>
|
||||||
|
</div>
|
||||||
|
<i title="Remove observed variables" class="fa fa-times utility-icon p-1" t-on-click.stop="() => this.store.clearObservedVariable()"></i>
|
||||||
|
</div>
|
||||||
|
<t t-foreach="store.observedVariables" t-as="observed" t-key="observed_index" t-if="observed.visible">
|
||||||
|
<ObjectTreeElement object="observed" index="observed_index"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
<div t-if="store.activeComponent.env.children.length > 0" id="env" class="details-panel ps-2 py-1">
|
<div t-if="store.activeComponent.env.children.length > 0" id="env" class="details-panel ps-2 py-1">
|
||||||
<div class="d-flex mb-2">
|
<div class="d-flex mb-2">
|
||||||
<div class="w-100" t-on-click.stop="(ev) => this.toggleCategory(ev, 'env')">
|
<div class="w-100" t-on-click.stop="(ev) => this.toggleCategory(ev, 'env')">
|
||||||
|
|||||||
+16
-4
@@ -64,6 +64,16 @@ export class ObjectTreeElement extends Component {
|
|||||||
show: this.props.object.contentType === "function",
|
show: this.props.object.contentType === "function",
|
||||||
action: () => this.store.inspectFunctionSource(this.props.object.path),
|
action: () => this.store.inspectFunctionSource(this.props.object.path),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Observe variable",
|
||||||
|
show: this.props.object.objectType !== "observed",
|
||||||
|
action: () => this.store.observeVariable(this.props.object.path),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Unobserve variable",
|
||||||
|
show: this.props.object.objectType === "observed",
|
||||||
|
action: () => this.store.clearObservedVariable(this.props.index),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Inject breakpoint on component",
|
title: "Inject breakpoint on component",
|
||||||
show: this.props.object.contentType === "array" && this.props.object.objectType === "hook",
|
show: this.props.object.contentType === "array" && this.props.object.objectType === "hook",
|
||||||
@@ -106,10 +116,12 @@ export class ObjectTreeElement extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupEditMode() {
|
setupEditMode() {
|
||||||
if (!this.state.editMode) {
|
if (
|
||||||
if (!this.props.object.hasChildren) {
|
!this.state.editMode &&
|
||||||
this.state.editMode = true;
|
!this.props.object.hasChildren &&
|
||||||
}
|
!(this.props.object.objectType === "observed")
|
||||||
|
) {
|
||||||
|
this.state.editMode = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export const store = reactive({
|
|||||||
version: "1.0",
|
version: "1.0",
|
||||||
},
|
},
|
||||||
selectedElement: null,
|
selectedElement: null,
|
||||||
|
observedVariables: [],
|
||||||
componentSearch: {
|
componentSearch: {
|
||||||
search: "",
|
search: "",
|
||||||
searchResults: [],
|
searchResults: [],
|
||||||
@@ -580,6 +581,27 @@ export const store = reactive({
|
|||||||
await this.loadComponentsTree(true);
|
await this.loadComponentsTree(true);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async observeVariable(path) {
|
||||||
|
this.observedVariables.push({ path: [...path], visible: false });
|
||||||
|
this.observedVariables = await evalFunctionInWindow(
|
||||||
|
"getObservedVariables",
|
||||||
|
[store.observedVariables],
|
||||||
|
store.activeFrame
|
||||||
|
);
|
||||||
|
await browserInstance.storage.local.set({
|
||||||
|
observedVariables: toRaw(this.observedVariables).map((o) => o.path),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
clearObservedVariable(index) {
|
||||||
|
if (index !== undefined) {
|
||||||
|
this.observedVariables.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
this.observedVariables = [];
|
||||||
|
}
|
||||||
|
browserInstance.storage.local.set({ observedVariables: toRaw(this.observedVariables) });
|
||||||
|
},
|
||||||
|
|
||||||
// Trigger the highlight on the component in the page
|
// Trigger the highlight on the component in the page
|
||||||
highlightComponent(path) {
|
highlightComponent(path) {
|
||||||
evalFunctionInWindow("highlightComponent", [path], this.activeFrame);
|
evalFunctionInWindow("highlightComponent", [path], this.activeFrame);
|
||||||
@@ -670,6 +692,14 @@ async function init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
|
// Refresh observed variables values every 200 ms
|
||||||
|
setInterval(async () => {
|
||||||
|
store.observedVariables = await evalFunctionInWindow(
|
||||||
|
"getObservedVariables",
|
||||||
|
[[...store.observedVariables]],
|
||||||
|
store.activeFrame
|
||||||
|
);
|
||||||
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
let rootRendersTimeout = false;
|
let rootRendersTimeout = false;
|
||||||
@@ -764,6 +794,16 @@ async function loadSettings() {
|
|||||||
storage.owlDevtoolsComponentsToggleBlacklist
|
storage.owlDevtoolsComponentsToggleBlacklist
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Observed variables
|
||||||
|
if (storage.observedVariables) {
|
||||||
|
store.observedVariables = [];
|
||||||
|
for (const path of storage.observedVariables) {
|
||||||
|
store.observedVariables.push({
|
||||||
|
path: [...path],
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to handle and store a batch of events coming from the page
|
// Function to handle and store a batch of events coming from the page
|
||||||
|
|||||||
@@ -143,7 +143,7 @@
|
|||||||
result.push(element);
|
result.push(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (obj.constructor.name !== "Object") {
|
if (obj.constructor && obj.constructor.name !== "Object") {
|
||||||
return obj.constructor.name + " {" + result.join(", ") + "}";
|
return obj.constructor.name + " {" + result.join(", ") + "}";
|
||||||
}
|
}
|
||||||
return "{" + result.join(", ") + "}";
|
return "{" + result.join(", ") + "}";
|
||||||
@@ -703,7 +703,7 @@
|
|||||||
try {
|
try {
|
||||||
obj = Object.getOwnPropertyDescriptor(obj, key.value).get.call(obj);
|
obj = Object.getOwnPropertyDescriptor(obj, key.value).get.call(obj);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
obj = e.toString();
|
obj = "Exception: " + e.toString();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "prototype getter":
|
case "prototype getter":
|
||||||
@@ -721,13 +721,13 @@
|
|||||||
try {
|
try {
|
||||||
obj = Object.getOwnPropertyDescriptor(obj, key.value).get;
|
obj = Object.getOwnPropertyDescriptor(obj, key.value).get;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
obj = e.toString();
|
obj = "Exception: " + e.toString();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
obj = obj[key.value];
|
obj = obj[key.value];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
obj = e.toString();
|
obj = "Exception: " + e.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -749,9 +749,11 @@
|
|||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
return this.getComponentNode(path);
|
return this.getComponentNode(path);
|
||||||
}
|
}
|
||||||
const componentNode = this.getComponentNode(path.slice(0, index));
|
const componentNode = this.getComponentNode(index === 1 ? path[0] : path.slice(0, index));
|
||||||
const obj = this.getObject(componentNode, path.slice(index));
|
if (componentNode) {
|
||||||
return obj;
|
return this.getObject(componentNode, path.slice(index));
|
||||||
|
}
|
||||||
|
return "Exception: component not found";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a modified version of an object node that has compatible format with the devtools ObjectTreeElement component
|
// Returns a modified version of an object node that has compatible format with the devtools ObjectTreeElement component
|
||||||
@@ -799,6 +801,11 @@
|
|||||||
child.name = "value";
|
child.name = "value";
|
||||||
obj = parentObj[1];
|
obj = parentObj[1];
|
||||||
break;
|
break;
|
||||||
|
case "getter":
|
||||||
|
child.name = key.value;
|
||||||
|
obj = parentObj[key.value];
|
||||||
|
break;
|
||||||
|
case "prototype getter":
|
||||||
case "set entry":
|
case "set entry":
|
||||||
case "map entry":
|
case "map entry":
|
||||||
case "item":
|
case "item":
|
||||||
@@ -814,6 +821,8 @@
|
|||||||
parentObj
|
parentObj
|
||||||
).findIndex((sym) => sym === key.value);
|
).findIndex((sym) => sym === key.value);
|
||||||
child.path[child.path.length - 1].value = key.value.toString();
|
child.path[child.path.length - 1].value = key.value.toString();
|
||||||
|
} else if (key.hasOwnProperty("symbolIndex")) {
|
||||||
|
obj = parentObj[Object.getOwnPropertySymbols(parentObj)[key.symbolIndex]];
|
||||||
} else {
|
} else {
|
||||||
obj = parentObj[key.value];
|
obj = parentObj[key.value];
|
||||||
}
|
}
|
||||||
@@ -845,7 +854,7 @@
|
|||||||
child.contentType = "set";
|
child.contentType = "set";
|
||||||
child.hasChildren = true;
|
child.hasChildren = true;
|
||||||
break;
|
break;
|
||||||
case obj.constructor.name === "Array":
|
case obj.constructor?.name === "Array":
|
||||||
child.contentType = "array";
|
child.contentType = "array";
|
||||||
child.hasChildren = obj.length > 0;
|
child.hasChildren = obj.length > 0;
|
||||||
break;
|
break;
|
||||||
@@ -853,11 +862,11 @@
|
|||||||
child.contentType = "function";
|
child.contentType = "function";
|
||||||
child.hasChildren = true;
|
child.hasChildren = true;
|
||||||
break;
|
break;
|
||||||
case obj instanceof Object:
|
case typeof obj === "object":
|
||||||
child.contentType = "object";
|
child.contentType = "object";
|
||||||
child.hasChildren =
|
child.hasChildren =
|
||||||
Object.keys(obj).length ||
|
Object.keys(obj).length > 0 ||
|
||||||
Object.getOwnPropertySymbols(obj).length ||
|
Object.getOwnPropertySymbols(obj).length > 0 ||
|
||||||
obj.constructor.name !== "Object";
|
obj.constructor.name !== "Object";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -1123,19 +1132,33 @@
|
|||||||
}
|
}
|
||||||
// The second element in the path will always be the root of the app
|
// The second element in the path will always be the root of the app
|
||||||
let node = [...this.apps][path[0]]?.root;
|
let node = [...this.apps][path[0]]?.root;
|
||||||
if (!node) {
|
if (path instanceof Array) {
|
||||||
return null;
|
if (!node) {
|
||||||
}
|
|
||||||
for (let i = 2; i < path.length; i++) {
|
|
||||||
// From this point onwards, it is an object path inside the component node
|
|
||||||
if (typeof path[i] !== "string") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (node.children.hasOwnProperty(path[i])) {
|
|
||||||
node = node.children[path[i]];
|
|
||||||
} else {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
for (let i = 2; i < path.length; i++) {
|
||||||
|
// From this point onwards, it is an object path inside the component node
|
||||||
|
if (typeof path[i] !== "string") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (node.children.hasOwnProperty(path[i])) {
|
||||||
|
node = node.children[path[i]];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const simplifiedPathArray = path.split("/");
|
||||||
|
if (node.name !== simplifiedPathArray[1]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (let i = 2; i < simplifiedPathArray.length; i += 2) {
|
||||||
|
const key = Reflect.ownKeys(node.children)[simplifiedPathArray[i]];
|
||||||
|
node = node.children[key];
|
||||||
|
if (node.name !== simplifiedPathArray[i + 1]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
@@ -1692,6 +1715,37 @@
|
|||||||
}
|
}
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
getObservedVariables(current) {
|
||||||
|
const res = [...current];
|
||||||
|
for (let i = 0; i < current.length; i++) {
|
||||||
|
const path = current[i].path;
|
||||||
|
const parent = this.getObjectProperty(path.slice(0, path.length - 1));
|
||||||
|
if (parent && !(typeof parent === "string" && parent.startsWith("Exception: "))) {
|
||||||
|
const result = this.serializeObjectChild(
|
||||||
|
parent,
|
||||||
|
path.at(-1),
|
||||||
|
0,
|
||||||
|
"observed",
|
||||||
|
path.slice(0, path.length - 1),
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
result.hasChildren = false;
|
||||||
|
result.visible = true;
|
||||||
|
const index = path.findIndex((key) => typeof key !== "string");
|
||||||
|
if (index > 1) {
|
||||||
|
const componentNode = this.getComponentNode(path.slice(0, index));
|
||||||
|
result.path = [this.getComponentSimplifiedPath(componentNode)].concat(
|
||||||
|
path.slice(index)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
res[i] = result;
|
||||||
|
} else {
|
||||||
|
res[i].visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
// Returns the path of the given component node
|
// Returns the path of the given component node
|
||||||
getComponentPath(componentNode) {
|
getComponentPath(componentNode) {
|
||||||
let path = [];
|
let path = [];
|
||||||
@@ -1708,6 +1762,23 @@
|
|||||||
path.unshift(index.toString());
|
path.unshift(index.toString());
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
// Returns the simplified path of the given component node (using component names and indexes)
|
||||||
|
getComponentSimplifiedPath(componentNode) {
|
||||||
|
let path = componentNode.name;
|
||||||
|
if (componentNode.parentKey) {
|
||||||
|
while (componentNode.parent) {
|
||||||
|
const previousKey = componentNode.parentKey;
|
||||||
|
componentNode = componentNode.parent;
|
||||||
|
path = `${componentNode.name}/${Reflect.ownKeys(componentNode.children).indexOf(
|
||||||
|
previousKey
|
||||||
|
)}/${path}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const appsArray = [...this.apps];
|
||||||
|
let index = appsArray.findIndex((app) => app === componentNode.app);
|
||||||
|
path = index.toString() + (path.length ? `/${path}` : "");
|
||||||
|
return path;
|
||||||
|
}
|
||||||
// Store the object into a temp window variable and log it to the console
|
// Store the object into a temp window variable and log it to the console
|
||||||
sendObjectToConsole(path) {
|
sendObjectToConsole(path) {
|
||||||
const obj = this.getObjectProperty(path);
|
const obj = this.getObjectProperty(path);
|
||||||
|
|||||||
Reference in New Issue
Block a user