This looks like a bug in the add-on.
The contentScripts.register
API internally converts the received code
parameter to a Blob
, which is represented in the about:memory
report as file(length=...)
.
I suggest to carefully examine your use of the contentScripts.register
API, and to check whether that can explain the memory usage. In particular, check whether the unregister()
method is always called.
From a quick look at your code, I can spot one hazard already: at https://github.com/menhera-org/TabArray/blob/4b0dd78e9d283ed6c5b2e2c9037c191f558fa5e2/src/overrides/ContentScriptRegistrar.ts#L43-L44,
await this.unregister(cookieStoreId);
// The above is basically:
// await this.contentScripts.get(cookieStoreId)?.unregister();
// this.contentScripts.delete(cookieStoreId);
this.contentScripts.set(cookieStoreId, await browser.contentScripts.register({
It is clear that the intended logic is “unregister before registering again”.
But the implementation is not atomic due to the async logic, and it is possible for register() to be called without ever having a matching unregister. That may be more obvious if we unroll your code to emphasize the presence of await
on separate lines:
/*1*/ let promise1 = this.contentScripts.get(cookieStoreId)?.unregister();
/*2*/ await promise1;
/*3*/ this.contentScripts.delete(cookieStoreId);
/*4*/ let promise2 = browser.contentScripts.register({ ... });
/*5*/ let registerResult = await promise2;
/*6*/ this.contentScripts.set(cookieStoreId, registerResult);
When the above logic is invoked twice without waiting for the completion of the first operation, then you will end up calling unregister()
for the initial script twice (/*1*/
), followed by registering two new scripts (/*4*/
). Finally, at /*6*/
the registered script is saved twice after each other, where the first one is no longer accessible by any code.
To fix this issue, you need to ensure that the above logic does not run concurrently, OR that unregister()
is guaranteed to be called when needed.