diff --git a/tools/devtools/src/devtools_app/context_menu/context_menu.js b/tools/devtools/src/devtools_app/context_menu/context_menu.js
new file mode 100644
index 00000000..7be2a673
--- /dev/null
+++ b/tools/devtools/src/devtools_app/context_menu/context_menu.js
@@ -0,0 +1,36 @@
+import { useStore } from "../store/store";
+
+const { Component, useEffect, useRef } = owl;
+
+export class ContextMenu extends Component {
+ static template = "devtools.ContextMenu";
+ static props = {
+ items: Array,
+ };
+ setup() {
+ this.store = useStore();
+ this.contextMenu = useRef("contextmenu");
+ useEffect(
+ (position) => {
+ const menu = this.contextMenu.el;
+ const menuWidth = menu.offsetWidth;
+ const menuHeight = menu.offsetHeight;
+ let { x, y } = position;
+ if (x + menuWidth > window.innerWidth) {
+ x = window.innerWidth - menuWidth;
+ }
+ if (y + menuHeight > window.innerHeight) {
+ y = window.innerHeight - menuHeight;
+ }
+ menu.style.left = x + "px";
+ // Need 25px offset because of the main navbar from the browser devtools
+ menu.style.top = y + "px";
+ },
+ () => [this.store.contextMenu?.position]
+ );
+ }
+ onClickItem(action) {
+ action();
+ this.store.contextMenu = null;
+ }
+}
diff --git a/tools/devtools/src/devtools_app/context_menu/context_menu.xml b/tools/devtools/src/devtools_app/context_menu/context_menu.xml
new file mode 100644
index 00000000..8eb29530
--- /dev/null
+++ b/tools/devtools/src/devtools_app/context_menu/context_menu.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.js b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.js
index 1934c968..52f8d250 100644
--- a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.js
+++ b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.js
@@ -1,4 +1,4 @@
-const { Component, useRef, useEffect } = owl;
+const { Component } = owl;
import { useStore } from "../../../store/store";
import { ObjectTreeElement } from "./object_tree_element/object_tree_element";
@@ -7,23 +7,64 @@ export class DetailsWindow extends Component {
static components = { ObjectTreeElement };
setup() {
this.store = useStore();
- this.contextMenu = useRef("contextmenu");
- this.contextMenuId = this.store.contextMenu.id++;
- this.contextMenuEvent;
- // Open the context menu when the ids match
- useEffect(
- (menuId) => {
- if (menuId === this.contextMenuId) {
- this.store.contextMenu.open(this.contextMenuEvent, this.contextMenu.el);
- }
+ }
+
+ get contextMenuItems() {
+ return [
+ {
+ title: "Inspect source code",
+ show: true,
+ action: () => this.store.inspectComponent("source", this.store.activeComponent.path),
},
- () => [this.store.contextMenu.activeMenu]
- );
+ {
+ title: "Store as global variable",
+ show: this.store.activeComponent.path.length !== 1,
+ action: () =>
+ this.store.logObjectInConsole([
+ ...this.store.activeComponent.path,
+ { type: "item", value: "component" },
+ ]),
+ },
+ {
+ title: "Inspect in Elements tab",
+ show: this.store.activeComponent.path.length !== 1,
+ action: () => this.store.inspectComponent("DOM", this.store.activeComponent.path),
+ },
+ {
+ title: "Force rerender",
+ show: this.store.activeComponent.path.length !== 1,
+ action: () => this.store.refreshComponent(this.store.activeComponent.path),
+ },
+ {
+ title: "Store observed states as global variable",
+ show: this.store.activeComponent.path.length !== 1,
+ action: () =>
+ this.store.logObjectInConsole([
+ ...this.store.activeComponent.path,
+ { type: "item", value: "subscriptions" },
+ ]),
+ },
+ {
+ title: "Inspect compiled template",
+ show: this.store.activeComponent.path.length !== 1,
+ action: () =>
+ this.store.inspectComponent("compiled template", this.store.activeComponent.path),
+ },
+ {
+ title: "Log raw template",
+ show: this.store.activeComponent.path.length !== 1,
+ action: () => this.store.inspectComponent("raw template", this.store.activeComponent.path),
+ },
+ {
+ title: "Store as global variable",
+ show: this.store.activeComponent.path.length === 1,
+ action: () => this.store.logObjectInConsole([...this.store.activeComponent.path]),
+ },
+ ];
}
openMenu(ev) {
- this.contextMenuEvent = ev;
- this.store.contextMenu.activeMenu = this.contextMenuId;
+ this.store.openContextMenu(ev, this.contextMenuItems);
}
toggleCategory(ev, category) {
diff --git a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.xml b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.xml
index ba477b71..2007adea 100644
--- a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.xml
+++ b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.xml
@@ -76,21 +76,5 @@
-
-
\ No newline at end of file
+
diff --git a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.js b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.js
index c50072ab..0cd37a0f 100644
--- a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.js
+++ b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.js
@@ -13,19 +13,8 @@ export class ObjectTreeElement extends Component {
menuTop: 0,
menuLeft: 0,
});
- this.contextMenu = useRef("contextmenu");
const inputRef = useRef("input");
this.store = useStore();
- this.contextMenuId = this.store.contextMenu.id++;
- this.contextMenuEvent,
- useEffect(
- (menuId) => {
- if (menuId === this.contextMenuId) {
- this.store.contextMenu.open(this.contextMenuEvent, this.contextMenu.el);
- }
- },
- () => [this.store.contextMenu.activeMenu]
- );
useEffect(
(editMode) => {
// Focus on the input when it is created
@@ -63,9 +52,23 @@ export class ObjectTreeElement extends Component {
return this.props.object.depth * 0.8 + 0.3;
}
+ get contextMenuItems() {
+ return [
+ {
+ title: "Store as global variable",
+ show: true,
+ action: () => this.store.logObjectInConsole(this.props.object.path),
+ },
+ {
+ title: "Inspect function source code",
+ show: this.props.object.contentType === "function",
+ action: () => this.store.inspectFunctionSource(this.props.object.path),
+ },
+ ];
+ }
+
openMenu(ev) {
- this.contextMenuEvent = ev;
- this.store.contextMenu.activeMenu = this.contextMenuId;
+ this.store.openContextMenu(ev, this.contextMenuItems);
}
setupEditMode() {
diff --git a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.xml b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.xml
index 6e1a2d40..17d904e4 100644
--- a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.xml
+++ b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.xml
@@ -4,7 +4,7 @@
-
diff --git a/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.js b/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.js
index 88ae721b..c7dee723 100644
--- a/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.js
+++ b/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.js
@@ -16,10 +16,7 @@ export class TreeElement extends Component {
searched: false,
});
this.store = useStore();
- this.contextMenu = useRef("contextmenu");
this.element = useRef("element");
- this.contextMenuId = this.store.contextMenu.id++;
- this.contextMenuEvent;
this.stringifiedPath = JSON.stringify(this.props.component.path);
// Scroll to the selected element when it changes
onMounted(() => {
@@ -38,15 +35,6 @@ export class TreeElement extends Component {
},
() => [this.props.component.selected]
);
- // Open the context menu when the ids match
- useEffect(
- (menuId) => {
- if (menuId === this.contextMenuId) {
- this.store.contextMenu.open(this.contextMenuEvent, this.contextMenu.el);
- }
- },
- () => [this.store.contextMenu.activeMenu]
- );
// Effect to apply a short highlight effect to the component when it is rendered
useEffect(
() => {
@@ -86,9 +74,86 @@ export class TreeElement extends Component {
return minimizeKey(this.props.component.key);
}
+ get contextMenuItems() {
+ return [
+ {
+ title: "Expand children",
+ show: true,
+ action: () => this.store.toggleComponentAndChildren(this.props.component, true),
+ },
+ {
+ title: "Fold all children",
+ show: true,
+ action: () => this.store.toggleComponentAndChildren(this.props.component, false),
+ },
+ {
+ title: "Fold direct children",
+ show: true,
+ action: () => this.store.foldDirectChildren(this.props.component),
+ },
+ {
+ title: "Inspect source code",
+ show: true,
+ action: () => this.store.inspectComponent("source", this.props.component.path),
+ },
+ {
+ title: "Store as global variable",
+ show: this.props.component.path.length !== 1,
+ action: () =>
+ this.store.logObjectInConsole([
+ ...this.props.component.path,
+ { type: "item", value: "component" },
+ ]),
+ },
+ {
+ title: "Inspect in Elements tab",
+ show: this.props.component.path.length !== 1,
+ action: () => this.store.inspectComponent("DOM", this.props.component.path),
+ },
+ {
+ title: "Force rerender",
+ show: this.props.component.path.length !== 1,
+ action: () => this.store.refreshComponent(this.props.component.path),
+ },
+ {
+ title: "Store observed states as global variable",
+ show: this.props.component.path.length !== 1,
+ action: () =>
+ this.store.logObjectInConsole([
+ ...this.props.component.path,
+ { type: "item", value: "subscriptions" },
+ ]),
+ },
+ {
+ title: "Inspect compiled template",
+ show: this.props.component.path.length !== 1,
+ action: () => this.store.inspectComponent("compiled template", this.props.component.path),
+ },
+ {
+ title: "Log raw template",
+ show: this.props.component.path.length !== 1,
+ action: () => this.store.inspectComponent("raw template", this.props.component.path),
+ },
+ {
+ title: "Store as global variable",
+ show: this.props.component.path.length === 1,
+ action: () => this.store.logObjectInConsole([...this.props.component.path]),
+ },
+ {
+ title: "Don't fold component by default",
+ show: this.store.settings.componentsToggleBlacklist.has(this.props.component.name),
+ action: () => this.toggleComponentToBlacklist(),
+ },
+ {
+ title: "Fold component by default",
+ show: !this.store.settings.componentsToggleBlacklist.has(this.props.component.name),
+ action: () => this.toggleComponentToBlacklist(),
+ },
+ ];
+ }
+
openMenu(ev) {
- this.contextMenuEvent = ev;
- this.store.contextMenu.activeMenu = this.contextMenuId;
+ this.store.openContextMenu(ev, this.contextMenuItems);
}
// Expand/fold the component node
diff --git a/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.xml b/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.xml
index c126b5da..28728db9 100644
--- a/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.xml
+++ b/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.xml
@@ -2,15 +2,15 @@
@@ -26,29 +26,6 @@
>
owl=
-
diff --git a/tools/devtools/src/devtools_app/devtools_window/devtools_window.js b/tools/devtools/src/devtools_app/devtools_window/devtools_window.js
index c2ac83f4..74a9c5ba 100644
--- a/tools/devtools/src/devtools_app/devtools_window/devtools_window.js
+++ b/tools/devtools/src/devtools_app/devtools_window/devtools_window.js
@@ -1,13 +1,14 @@
const { Component } = owl;
-import { ComponentsTab } from "./components_tab/components_tab";
-import { Tab } from "./tab/tab";
-import { ProfilerTab } from "./profiler_tab/profiler_tab";
+import { ContextMenu } from "../context_menu/context_menu";
import { useStore } from "../store/store";
+import { ComponentsTab } from "./components_tab/components_tab";
+import { ProfilerTab } from "./profiler_tab/profiler_tab";
+import { Tab } from "./tab/tab";
export class DevtoolsWindow extends Component {
static props = [];
static template = "devtools.DevtoolsWindow";
- static components = { ComponentsTab, Tab, ProfilerTab };
+ static components = { ComponentsTab, Tab, ProfilerTab, ContextMenu };
setup() {
this.store = useStore();
}
diff --git a/tools/devtools/src/devtools_app/devtools_window/devtools_window.xml b/tools/devtools/src/devtools_app/devtools_window/devtools_window.xml
index b75896d2..a8936d5d 100644
--- a/tools/devtools/src/devtools_app/devtools_window/devtools_window.xml
+++ b/tools/devtools/src/devtools_app/devtools_window/devtools_window.xml
@@ -28,6 +28,7 @@
Owl is not loaded on this page.
+
diff --git a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.js b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.js
index 0ba34b31..7bf28362 100644
--- a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.js
+++ b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.js
@@ -1,24 +1,13 @@
import { minimizeKey } from "../../../../utils";
import { useStore } from "../../../store/store";
-const { Component, useEffect, useRef } = owl;
+const { Component } = owl;
export class Event extends Component {
static template = "devtools.Event";
setup() {
this.store = useStore();
- this.componentContextMenu = useRef("componentContextmenu");
- this.componentContextMenuId = this.store.contextMenu.id++;
- this.contextMenuEvent,
- useEffect(
- (menuId) => {
- if (menuId === this.componentContextMenuId) {
- this.store.contextMenu.open(this.contextMenuEvent, this.componentContextMenu.el);
- }
- },
- () => [this.store.contextMenu.activeMenu]
- );
}
// Formatting for displaying the key of the component
@@ -54,13 +43,65 @@ export class Event extends Component {
}
}
- openComponentMenu(ev) {
+ get contextMenuItems() {
+ return [
+ {
+ title: "Inspect source code",
+ show: true,
+ action: () => this.store.inspectComponent("source", this.props.event.path),
+ },
+ {
+ title: "Store as global variable",
+ show: this.props.event.path.length !== 1,
+ action: () =>
+ this.store.logObjectInConsole([
+ ...this.props.event.path,
+ { type: "item", value: "component" },
+ ]),
+ },
+ {
+ title: "Inspect in Elements tab",
+ show: this.props.event.path.length !== 1,
+ action: () => this.store.inspectComponent("DOM", this.props.event.path),
+ },
+ {
+ title: "Force rerender",
+ show: this.props.event.path.length !== 1,
+ action: () => this.store.refreshComponent(this.props.event.path),
+ },
+ {
+ title: "Store observed states as global variable",
+ show: this.props.event.path.length !== 1,
+ action: () =>
+ this.store.logObjectInConsole([
+ ...this.props.event.path,
+ { type: "item", value: "subscriptions" },
+ ]),
+ },
+ {
+ title: "Inspect compiled template",
+ show: this.props.event.path.length !== 1,
+ action: () => this.store.inspectComponent("compiled template", this.props.event.path),
+ },
+ {
+ title: "Log raw template",
+ show: this.props.event.path.length !== 1,
+ action: () => this.store.inspectComponent("raw template", this.props.event.path),
+ },
+ {
+ title: "Store as global variable",
+ show: this.props.event.path.length === 1,
+ action: () => this.store.logObjectInConsole([...this.props.event.path]),
+ },
+ ];
+ }
+
+ openMenu(ev) {
if (this.props.event.type === "destroy") {
return;
} else {
ev.preventDefault();
- this.contextMenuEvent = ev;
- this.store.contextMenu.activeMenu = this.componentContextMenuId;
+ this.store.openContextMenu(ev, this.contextMenuItems);
}
}
}
diff --git a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.xml b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.xml
index 4c152e3a..369d3a26 100644
--- a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.xml
+++ b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.xml
@@ -8,11 +8,11 @@
t-att-class="{'fa-caret-right': !props.event.toggled, 'fa-caret-down': props.event.toggled}"
t-attf-style="visibility: {{props.event.origin ? '' : 'hidden'}};"
/>
- :
+ :
<
@@ -29,10 +29,10 @@
- origin:
- <
@@ -43,22 +43,6 @@
-
diff --git a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.js b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.js
index 8ad57b90..95ede0c6 100644
--- a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.js
+++ b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.js
@@ -1,7 +1,7 @@
import { minimizeKey } from "../../../../utils";
import { useStore } from "../../../store/store";
-const { Component, useRef, useEffect } = owl;
+const { Component } = owl;
export class EventNode extends Component {
static template = "devtools.EventNode";
@@ -10,33 +10,89 @@ export class EventNode extends Component {
setup() {
this.store = useStore();
- this.nodeContextMenu = useRef("nodeContextMenu");
- this.nodeContextMenuId = this.store.contextMenu.id++;
- this.componentContextMenu = useRef("componentContextmenu");
- this.componentContextMenuId = this.store.contextMenu.id++;
- this.contextMenuEvent,
- useEffect(
- (menuId) => {
- if (menuId === this.nodeContextMenuId) {
- this.store.contextMenu.open(this.contextMenuEvent, this.nodeContextMenu.el);
- }
- if (menuId === this.componentContextMenuId) {
- this.store.contextMenu.open(this.contextMenuEvent, this.componentContextMenu.el);
- }
- },
- () => [this.store.contextMenu.activeMenu]
- );
}
get eventPadding() {
return this.props.event.depth * 0.8 + 0.3;
}
+ get nodeContextMenuItems() {
+ return [
+ {
+ title: "Expand children",
+ show: true,
+ action: () => this.store.toggleEventAndChildren(this.props.event, true),
+ },
+ {
+ title: "Fold all children",
+ show: true,
+ action: () => this.store.toggleEventAndChildren(this.props.event, false),
+ },
+ {
+ title: "Fold direct children",
+ show: true,
+ action: () => this.store.foldDirectChildren(this.props.event),
+ },
+ ];
+ }
+
+ get componentContextMenuItems() {
+ return [
+ {
+ title: "Inspect source code",
+ show: true,
+ action: () => this.store.inspectComponent("source", this.props.event.path),
+ },
+ {
+ title: "Store as global variable",
+ show: this.props.event.path.length !== 1,
+ action: () =>
+ this.store.logObjectInConsole([
+ ...this.props.event.path,
+ { type: "item", value: "component" },
+ ]),
+ },
+ {
+ title: "Inspect in Elements tab",
+ show: this.props.event.path.length !== 1,
+ action: () => this.store.inspectComponent("DOM", this.props.event.path),
+ },
+ {
+ title: "Force rerender",
+ show: this.props.event.path.length !== 1,
+ action: () => this.store.refreshComponent(this.props.event.path),
+ },
+ {
+ title: "Store observed states as global variable",
+ show: this.props.event.path.length !== 1,
+ action: () =>
+ this.store.logObjectInConsole([
+ ...this.props.event.path,
+ { type: "item", value: "subscriptions" },
+ ]),
+ },
+ {
+ title: "Inspect compiled template",
+ show: this.props.event.path.length !== 1,
+ action: () => this.store.inspectComponent("compiled template", this.props.event.path),
+ },
+ {
+ title: "Log raw template",
+ show: this.props.event.path.length !== 1,
+ action: () => this.store.inspectComponent("raw template", this.props.event.path),
+ },
+ {
+ title: "Store as global variable",
+ show: this.props.event.path.length === 1,
+ action: () => this.store.logObjectInConsole([...this.props.event.path]),
+ },
+ ];
+ }
+
openNodeMenu(ev) {
if (this.props.event.children.length) {
ev.preventDefault();
- this.contextMenuEvent = ev;
- this.store.contextMenu.activeMenu = this.nodeContextMenuId;
+ this.store.openContextMenu(ev, this.nodeContextMenuItems);
}
}
@@ -45,8 +101,7 @@ export class EventNode extends Component {
return;
} else {
ev.preventDefault();
- this.contextMenuEvent = ev;
- this.store.contextMenu.activeMenu = this.componentContextMenuId;
+ this.store.openContextMenu(ev, this.componentContextMenuItems);
}
}
diff --git a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.xml b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.xml
index caa7e565..ce3c3bf1 100644
--- a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.xml
+++ b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.xml
@@ -2,8 +2,8 @@
- :
- <:
+ <
key=
@@ -28,29 +28,6 @@
-
-
diff --git a/tools/devtools/src/devtools_app/store/store.js b/tools/devtools/src/devtools_app/store/store.js
index b749d137..e2d493fd 100644
--- a/tools/devtools/src/devtools_app/store/store.js
+++ b/tools/devtools/src/devtools_app/store/store.js
@@ -11,30 +11,7 @@ export const store = reactive({
darkmode: false,
componentsToggleBlacklist: new Set(),
},
- contextMenu: {
- id: 0,
- activeMenu: -1,
- // Opens the context menu corresponding with the given menu html element
- open(event, menu) {
- const menuWidth = menu.offsetWidth;
- const menuHeight = menu.offsetHeight;
- let x = event.clientX;
- let y = event.clientY;
- if (x + menuWidth > window.innerWidth) {
- x = window.innerWidth - menuWidth;
- }
- if (y + menuHeight > window.innerHeight) {
- y = window.innerHeight - menuHeight;
- }
- menu.style.left = x + "px";
- // Need 25px offset because of the main navbar from the browser devtools
- menu.style.top = y - 25 + "px";
- },
- // Close the currently displayed context menu
- close() {
- this.activeMenu = -1;
- },
- },
+ contextMenu: null,
isFirefox: IS_FIREFOX,
frameUrls: ["top"],
activeFrame: "top",
@@ -93,6 +70,16 @@ export const store = reactive({
evalFunctionInWindow("disableHTMLSelector", [], this.activeFrame);
},
+ openContextMenu(event, items) {
+ this.contextMenu = {
+ position: {
+ x: event.clientX,
+ y: event.clientY,
+ },
+ items,
+ };
+ },
+
// Load all data related to the components tree using the global hook loaded on the page
// Use fromOld to specify if we want to keep most of the toggled/selected data of the old tree
// when generating the new one
@@ -646,8 +633,9 @@ async function init() {
store.updateIFrameList();
// Global listeners to close the currently shown context menu when the user clicks or opens another
- document.addEventListener("click", () => store.contextMenu.close(), { capture: true });
- document.addEventListener("contextmenu", () => store.contextMenu.close(), { capture: true });
+ document.addEventListener("click", () => (store.contextMenu = null), { capture: true });
+ document.addEventListener("contextmenu", () => (store.contextMenu = null), { capture: true });
+ window.addEventListener("blur", () => (store.contextMenu = null), { capture: true });
// Make sure the events recorder is at its initial state in every frame
for (const frame of store.frameUrls) {