Unhandled Promise rejection: Converting circular structure to JSON for DomNodes.

130 views
Skip to first unread message

Yureshwar Ravuri

unread,
Aug 7, 2023, 11:05:32 AM8/7/23
to Chromium Extensions
Hi,

We are working on an Open Source Chrome extension: Digital Assistant Client

We are trying to record the user click by converting the clicked node to json with domJson library and we are stringifying the json. While stringifying in some angular websites we are getting error as follows

Unhandled Promise rejection: Converting circular structure to JSON
--> starting at object with constructor 'Object'
    |     property 'blueprint' -> object with constructor 'Array'
    --- index 1 closes the circle ; Zone: <root> ; Task: Promise.then ; Value: TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    |     property 'blueprint' -> object with constructor 'Array'
    --- index 1 closes the circle


We tried to use the following extensions

https://www.npmjs.com/package/flatted
https://www.npmjs.com/package/circular-to-json

But we are unable to resolve the issue. Can anyone help us on how we can stringify the domNodes which has circular references to parent objects and so on.

Patrick Kettner

unread,
Aug 7, 2023, 11:52:46 AM8/7/23
to Yureshwar Ravuri, Chromium Extensions
Can you explain what you mean by "we are unable to resolve the issue". What happens when you use those packages?

--
You received this message because you are subscribed to the Google Groups "Chromium Extensions" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-extens...@chromium.org.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/chromium-extensions/d8a53a8e-097a-477e-bf1a-3487430914c7n%40chromium.org.

yureshwar ravuri

unread,
Aug 7, 2023, 12:03:27 PM8/7/23
to Patrick Kettner, Chromium Extensions
It produces the same output @patrick

Thanks & Regards,
Yureshwar Ravuri
Mob: +91 9030003889
Mail: yure...@gmail.com

Patrick Kettner

unread,
Aug 7, 2023, 12:07:43 PM8/7/23
to yureshwar ravuri, Chromium Extensions
The same output? Or the same error?
This would be an error from V8, and not something extension specific. If multiple circular JSON packages are not helping, it sounds like there may be a bug in your code. Flatted is very heavily used.
Can you share the steps on how to replicate the error?

wOxxOm

unread,
Aug 7, 2023, 12:39:35 PM8/7/23
to Chromium Extensions, Patrick Kettner, Chromium Extensions, yureshwar ravuri
Judging by the name "blueprint", which is not a part of the standard DOM, you're running your code in the MAIN world. Do you really need it? The MAIN world is unsafe as the other code of the page can intercept and hijack your data unless you implement very elaborate tricks used by Tampermonkey/Violentmonkey (and those trick don't work in ManifestV3). If you need the MAIN world for some specific task then split out the relevant code into a separate script while the main part of your script should run in the default world which is ISOLATED where it won't see the custom DOM expandos added by frameworks like Angular or React.

yureshwar ravuri

unread,
Aug 7, 2023, 1:57:29 PM8/7/23
to Patrick Kettner, Chromium Extensions
Hi Patrick,

It's "the same error". For replicating the error i have encountered this when i am testing an angular application(version="13.0.3") with my chrome extension. 

1. Attach a custom event to clickable object 
2. Get that node value converted to json with domJson library
3. Use the Flatted library to remove the circular reference / Try to stringify the json object with JSON.Stringify you will see the same error which i have posted.

You can download our plugin from here and can have a look it's open source you can find the code at recordService.ts at line number 113

Thanks & Regards,
Yureshwar Ravuri
Mob: +91 9030003889
Mail: yure...@gmail.com

yureshwar ravuri

unread,
Aug 7, 2023, 2:02:28 PM8/7/23
to wOxxOm, Chromium Extensions, Patrick Kettner
Hi Woxxom,

Thanks for your reply. There is one challenge if we want to run our code on an isolated world instead of the main world as the node object cannot be shared from the main world. If there is any chance that the node object can be shared from the main world we could have used that approach. But we use the same codebase to build sdk which can be integrated individually into applications. 

Thanks & Regards,
Yureshwar Ravuri
Mob: +91 9030003889
Mail: yure...@gmail.com

wOxxOm

unread,
Aug 7, 2023, 2:10:47 PM8/7/23
to Chromium Extensions, yureshwar ravuri, Chromium Extensions, Patrick Kettner, wOxxOm
You can send a DOM node between worlds like this:

window.dispatchEvent(new MouseEvent('foo', {relatedTarget: node }));

and receive it like this:

window.addEventListener('foo', e => { console.log(e.relatedTarget); });

To exchange normal data you can use CustomEvent, which supports almost all structured-clonable types.

yureshwar ravuri

unread,
Aug 22, 2023, 1:23:28 PM8/22/23
to wOxxOm, Chromium Extensions, Patrick Kettner
Hi Woxxom,

I tried your approach but the dom node data is not getting shared between the worlds that is from webpage i am not able to share to extension background script. Is there any way i could share the dom node? 

But inside the same page content script the additional data which is added by angular framework is getting removed. If there is no option of sharing the domnode between content script and background script then i can utilize the content script option but i have few queries as mentioned below

1. When we dispatch event and if the page is getting redirected to another website where the application is not SPA(single page application) will the addEventListner will receive the data before it gets redirected?
2. Can any frameworks can stop dispatchEvent or addEventListner functionality?

Thanks for your help in advance.

Thanks & Regards,
Yureshwar Ravuri
Mob: +91 9030003889
Mail: yure...@gmail.com

wOxxOm

unread,
Aug 22, 2023, 2:03:47 PM8/22/23
to Chromium Extensions, yureshwar ravuri, Chromium Extensions, Patrick Kettner, wOxxOm
You can use CustomEvent to send the other "DOM expando" data separately in an additional message.
1. dispatchEvent is synchronous so the event listener is triggered immediately before execution returns to your next statement after dispatchEvent.
2. you can use a random event id

yureshwar ravuri

unread,
Sep 5, 2023, 6:30:35 AM9/5/23
to wOxxOm, Chromium Extensions
Hi Woxxom,

I tried what you told me.

When I pass the node from inside world(page script) to outside world (content script) using MouseEvent(), the extra angular DOM attributes are indeed stripped. This is exactly what I wanted.

Now, our application is designed to be used both as a browser extension OR as an SDK. : https://github.com/Digital-Assistant/Digital_Assistant_Client#integrate-as-sdk-into-applications

While your suggestion works for Browser Extension (transferring node from inside world to inside world), it is not working as intended in the SDK (inside world to inside world).
i.e the elements are not stripped when I pass the node element to another method in the inside world itself.  

I am attaching click event like this to clickable elements

node.addEventListener('click', async function (event: any) {
await recordUserClick(node, event);
}, {once: false});

Under recordUserClick i am sending the dom node for removing 'dom expandos' like below

document.dispatchEvent(new MouseEvent('UDANodeData', {relatedTarget: node}));


under content script i was getting the node object as given below

document.addEventListener("UDANodeData", function(data) {
console.log('nodedata listener at content script');
console.log({node: data.relatedTarget});
});

under page script i am doing it as given below

document.addEventListener("UDANodeData", function(data: any) {
console.log('nodedata listener at page script');
console.log({node: data.relatedTarget});
});


Please let me know how we can achieve the outside world under the same page script.


Do you have any suggestions to make it work for SDK too

Thanks & Regards,
Yureshwar Ravuri
Mob: +91 9030003889
Mail: yure...@gmail.com

wOxxOm

unread,
Sep 5, 2023, 7:18:16 AM9/5/23
to Chromium Extensions, yureshwar ravuri, Chromium Extensions, wOxxOm
There's no solution. The only workaround I see, assuming the receiver's listener is added by your SDK, is replacing the received node with a proxy, however note that it has its own problems such as the impossibility of === comparison to a real node object in DOM or [...parentElem.children].indexOf(node) won't work on a proxied object.

const stripExpandoHandler = {
  get: (node, key) => !Object.hasOwn(node, key) ? node[key] : undefined,
  has: (node, key) => !Object.hasOwn(node, key) && (key in node),
}
document.addEventListener("UDANodeData", function(e: MouseEvent) {
  const node = new Proxy(data.relatedTarget, stripExpandoHandler);
  console.log(node);
});

yureshwar ravuri

unread,
Sep 8, 2023, 11:41:48 AM9/8/23
to wOxxOm, Chromium Extensions
Hi Woxxom,

Just to update you on the resolution that we were able to achieve by using cloneNode(deep) , when we clone the node the expandos which are having circular references disappear. 

Thanks for helping us in this regard.  

Thanks & Regards,
Yureshwar Ravuri
Mob: +91 9030003889
Mail: yure...@gmail.com

wOxxOm

unread,
Sep 8, 2023, 12:23:13 PM9/8/23
to Chromium Extensions, yureshwar ravuri, Chromium Extensions, wOxxOm
Yeah, cloning works, of course, I just dimissed this rute thinking you want the actual node that belongs to the document.
Reply all
Reply to author
Forward
0 new messages