From 8fd9cf37cfd73df98e30acb79b3102db2f4aa5ae Mon Sep 17 00:00:00 2001 From: Julien Carion Date: Thu, 16 Jul 2026 11:03:41 +0200 Subject: [PATCH] [FIX] devtools: get OWL 3 root nodes through the mount promise Since https://github.com/odoo/owl/commit/3700cad419d609a39e842da44d5cf054f29af747, roots created by app.createRoot() no longer expose their ComponentNode: root.node is undefined, so getRoots() returned an array of undefined for every OWL 3 app and the extension could not inspect any component tree. The node is still reachable through the only remaining public surface: root.promise resolves with the root component instance, whose __owl__ property is its ComponentNode. Since getRoots() is synchronous, cache each root's node in a WeakMap when its promise resolves, and post a RefreshApps message at that point so the devtools re-render once the tree becomes inspectable. Roots whose mount fails resolve to nothing and are simply skipped. root.node is still used when present, for compatibility with earlier 3.x versions that exposed it directly. --- .../page_scripts/owl_devtools_global_hook.js | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tools/devtools/src/page_scripts/owl_devtools_global_hook.js b/tools/devtools/src/page_scripts/owl_devtools_global_hook.js index 4c54aacae..00784f370 100644 --- a/tools/devtools/src/page_scripts/owl_devtools_global_hook.js +++ b/tools/devtools/src/page_scripts/owl_devtools_global_hook.js @@ -51,6 +51,9 @@ this.requestedFrame = false; this.enabledSelector = false; this.eventsBatch = []; + // ComponentNode of each OWL 3 root, cached as its mount promise resolves + // (roots do not expose their node synchronously anymore). null = pending. + this.rootNodes = new WeakMap(); this.breakpointsClassMap = new Map(); this.breakpointsHookMap = new Map(); // Object which defines how different types of data should be displayed when passed to the devtools @@ -204,7 +207,31 @@ getRoots(app) { const version = this.getAppVersion(app); if (version.startsWith("3")) { - return [...app.roots].map((root) => root.node); + const nodes = []; + for (const root of app.roots) { + // TODO: Early OWL 3 versions exposed the ComponentNode directly on the root, remove when stable + if (root.node) { + nodes.push(root.node); + continue; + } + if (this.rootNodes.has(root)) { + const node = this.rootNodes.get(root); + if (node) { + nodes.push(node); + } + } else { + this.rootNodes.set(root, null); + root.promise.then( + (component) => { + this.rootNodes.set(root, component.__owl__); + window.top.postMessage({ source: "owl-devtools", type: "RefreshApps" }); + }, + // Root failed to mount: there is no component tree to inspect + () => {} + ); + } + } + return nodes; } // OWL 2: main root at index 0, subRoots at index 1+ const roots = [];