inspect icon indicating copy to clipboard operation
inspect copied to clipboard

Inspector and parent window freeze when attempting to serialize an HTML element

Open mutewinter opened this issue 1 year ago • 2 comments

Took me a while to track this one down and I believe it's related to:

https://github.com/statelyai/xstate/issues/4645 https://github.com/statelyai/xstate/issues/4985

I was able to patch @statelyai/inspect package at version 0.4.0 to fix this issue:

diff --git a/dist/index.js b/dist/index.js
index 844efec2f70099dd0a5bc807d191a9f54bef28c6..237e6ef3a9e0a832781e33e043b906b26c2aa839 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -111,6 +111,9 @@ function idleCallback(cb) {
 
 // src/createInspector.ts
 var import_safe_stable_stringify = __toESM(require("safe-stable-stringify"));
+var safe_stable_stringify = import_safe_stable_stringify.configure({
+  maximumDepth: 10
+})
 function getRoot(actorRef) {
   let marker = actorRef;
   do {
@@ -273,7 +276,7 @@ function convertXStateEvent(inspectionEvent) {
       return {
         type: "@xstate.snapshot",
         event: inspectionEvent.event,
-        snapshot: JSON.parse((0, import_safe_stable_stringify.default)(inspectionEvent.snapshot)),
+        snapshot: JSON.parse((0, safe_stable_stringify)(inspectionEvent.snapshot)),
         sessionId: inspectionEvent.actorRef.sessionId,
         _version: package_default.version,
         createdAt: Date.now().toString(),
@@ -323,7 +326,7 @@ function createBrowserInspector(options) {
     ...defaultInspectorOptions,
     url: "https://stately.ai/inspect",
     filter: () => true,
-    serialize: (inspectionEvent) => JSON.parse((0, import_fast_safe_stringify.default)(inspectionEvent)),
+    serialize: (inspectionEvent) => JSON.parse((0, import_fast_safe_stringify.default)(inspectionEvent, null, 2, { depthLimit: 10, edgesLimit: 10 })),
     autoStart: true,
     iframe: null,
     ...options,

I'm not sure if these max depth values are the best they could be, but this did resolve my issue.

mutewinter avatar Sep 25 '24 14:09 mutewinter

Please provide a CodeSandbox reproduction. You can use one of the templates here: https://github.com/statelyai/xstate?tab=readme-ov-file#templates

davidkpiano avatar Sep 30 '24 04:09 davidkpiano

Same issue. We are storing complex BabylonJS objects with circular references in our machine context which is causing the inspector to freeze the whole app when it tries to serialize our snapshots. An option to specify maximum depth would solve this without us needing to manually patch the library. I'm currently experimenting with the serialize option on this package, but it's poorly documented so I'm hitting a lot of walls.

chevcast avatar May 14 '25 13:05 chevcast

Just hit this when storing an HTMLDivElement ref in context.

Here is the repo for my use case.

https://codesandbox.io/p/devbox/weathered-silence-85pjyp

My patch is a bit different. I can use the inspector again with this.

diff --git a/node_modules/@statelyai/inspect/dist/index.js b/node_modules/@statelyai/inspect/dist/index.js
index 844efec..47f6483 100644
--- a/node_modules/@statelyai/inspect/dist/index.js
+++ b/node_modules/@statelyai/inspect/dist/index.js
@@ -111,6 +111,9 @@ function idleCallback(cb) {
 
 // src/createInspector.ts
 var import_safe_stable_stringify = __toESM(require("safe-stable-stringify"));
+var safe_stable_stringify = import_safe_stable_stringify.configure({
+   maximumDepth: 10
+})
 function getRoot(actorRef) {
   let marker = actorRef;
   do {
@@ -273,7 +276,7 @@ function convertXStateEvent(inspectionEvent) {
       return {
         type: "@xstate.snapshot",
         event: inspectionEvent.event,
-        snapshot: JSON.parse((0, import_safe_stable_stringify.default)(inspectionEvent.snapshot)),
+        snapshot: JSON.parse((0, safe_stable_stringify)(inspectionEvent.snapshot)),
         sessionId: inspectionEvent.actorRef.sessionId,
         _version: package_default.version,
         createdAt: Date.now().toString(),
@@ -323,7 +326,7 @@ function createBrowserInspector(options) {
     ...defaultInspectorOptions,
     url: "https://stately.ai/inspect",
     filter: () => true,
-    serialize: (inspectionEvent) => JSON.parse((0, import_fast_safe_stringify.default)(inspectionEvent)),
+    serialize: (inspectionEvent) => JSON.parse((0, safe_stable_stringify)(inspectionEvent)),
     autoStart: true,
     iframe: null,
     ...options,

jensen avatar Nov 08 '25 19:11 jensen

@jensen we actually came up with a fun workaround a while back. For complex objects simply store object references instead of the actual objects. To do this just wrap your complex object in a closure.

Instead of this:

context: {
  htmlElement: myDiv
}

Do this:

context: {
  htmlElement: () => myDiv
}

Because the closures are just functions, they get completely stripped out during serialization, avoiding the problem entirely while still being able to access those objects in the actors/actions where they are needed.

Do the same for any event inputs or actor outputs as those all get serialized as well.

chevcast avatar Nov 08 '25 19:11 chevcast

I'd appreciate a PR for this, or I can get to it this week.

davidkpiano avatar Nov 08 '25 21:11 davidkpiano