Performance issues due to add-on incompatiblity

Hello,

I’ve noticed some big performance issues when my add-on GroupSpeedDial is combined with some other add-ons like Adblock Plus, Ghostery and especially Flash Video Downloader.
Now since we are in WebExtension era, I would expect this is not really possible anymore.

What I found out, is that listener browser.webRequest.onBeforeRequest registered in these add-ons will fire for each image displayed in my add-on page. These images are “data:image” encoded in base64 and the event object looks like this:

{
  "requestId": "fakeRequest-2905",
  "url": "data:image/png;base64,iVB...",
  "originUrl": "moz-extension://f3eb16fa-df39-42f1-941f-f6d36b36ce12/dial.html",
  "documentUrl": "moz-extension://f3eb16fa-df39-42f1-941f-f6d36b36ce12/dial.html",
  "type": "video",
  "timeStamp": 1536489946175,
  "frameId": 6442451129,
  "parentFrameId": -1,
  "tabId": 9,
  "tab": {
    "id": 9,
    "index": 6,
    "windowId": 1,
    "highlighted": true,
    "active": true,
    "attention": false,
    "pinned": false,
    "status": "complete",
    "hidden": false,
    "discarded": false,
    "incognito": false,
    "width": 1680,
    "height": 1128,
    "lastAccessed": 1536490035783,
    "audible": false,
    "mutedInfo": {
      "muted": false
    },
    "isArticle": false,
    "isInReaderMode": false,
    "sharingState": {
      "camera": false,
      "microphone": false
    },
    "cookieStoreId": "firefox-default",
    "url": "moz-extension://f3eb16fa-df39-42f1-941f-f6d36b36ce12/dial.html#2",
    "title": "GroupSpeedDial",
    "favIconUrl": "data:image/png;base64,iVB..."
  },
  "responseHeaders": null,
  "contentType": null,
  "size": null,
  "tabUrl": "moz-extension://f3eb16fa-df39-42f1-941f-f6d36b36ce12/dial.html#2",
  "tabTitle": "GroupSpeedDial",
  "filename": null,
  "ext": null,
  "contentRange": null,
  "contentDisp": null,
  "headers": {},
  "foundMedia": null
}

Is this correct behavior?
Should add-on be receiving this event from another add-on?
And mostly, why is this causing performance issues in my add-on? Sometimes it even shows the yellow bar saying that my add-on page is slowing down your browser (which is not true!).

Profiler info from Flash Video Downloader when switching betwen two groups with few dials:

Could a data:image/png URI be used for a web request? Some data: URI’s might, but it’s hard to picture this content type triggering one. I wonder how this is all filtered. Someone should compare Chrome’s implementation.

I have a (private) add-on that shows a panel with thumbnails. In my current test in FF63 it shows ~80 different JPEGs, 120x90px, each displayed twice.
They are stored as Blobs in an indexedDB. If they need to be fetched from there the panel opens in ~1.5 seconds (and that includes loading and rendering of quite a bit of other stuff as well). If the images Blobs are already in memory and can be loaded from the blob:-URLs directly, its considerably soother.

So, use blob:-URLs?

1 Like

Interesting idea with the blob. I will need to check the docs, I’m not sure if I can send blobs from my background script (which is required for Private Browsing when IndexedDB is not accessible from add-on page). Although I can still encode them to base64 string and then back just for this use-case.

And I’m really not looking forward to the refactoring and data migration :frowning:, it’s good though I’m kind of obsessed with optimizations :slight_smile:.

Anyway I will try it and I will re-check whether the even handler will fire for blobs as well.

Also, should I create a bug for this? Even if the event is correctly fired, I still don’t like the hang of my add-on when all those tasks should be asynchronous and non-blocking.

EDIT:
@NilkasG what do you mean by “already in memory”? You mean you don’t call the URL.revokeObjectURL() to release them once you create them with URL.createObjectURL?
Also can I store the blob in IndexedDB as any other primitive value? Or the whole object needs to be a blob? I can’t find a proper example.

EDIT 2:
Bug reported:


Video demonstration:

Sorry, didn’t see your edit.

what do you mean by “already in memory”? You mean you don’t call the URL.revokeObjectURL() to release them once you create them with URL.createObjectURL?

I basically do reference counting on the blob:-URLs, so if the page is already open in e.g. a tab and then gets opened in the panel as well, the URLs aren’t revoked yet, and since the reference is only dropped after a short timeout, this also works if the panel has just recently closed (e.g. due to a blur) and is reopened. But I do call URL.revokeObjectURL() eventually ^^

Also can I store the blob in IndexedDB as any other primitive value? Or the whole object needs to be a blob?

Don’t really know, as I store them in a separate table/collection with just the ID and the Blob, because I need to store and retrieve the blobs independently of other information keyed to the IDs.
But MDN says it pretty clearly:

When an object or array is stored, the properties and values in that object or array can also be anything that is a valid value.

Blobs and files can be stored

1 Like

Thank you for the info!

I’ve just finished testing the proof of concept implementation and this is what I learned:

  1. blobs can be indeed stored in IndexedDB as any other values

  2. fetching blobs from IndexeDB seems to have no performance impact at all! It’s like they are not being fetched, just the reference. This is a big difference compare to long base64 strings!

  3. using blobs completely fixed the performance bottleneck in “Flash Video Downloader” - probably the event won’t fire for blob: url

  4. data migration sucks! Especially if it’s related to IndexedDB and the data migration process is asynchronous!

  5. initial drawing of images is now a bit slower - this is a bit disappointing because I can see it’s slower :frowning: . However once the images stays in memory (not being freed by revokeObjectURL) it’s super fast again.

Now since createObjectURL lifetime is tied to document, there is a crazy idea to create them in background script and that way keeping them cached.
Maybe with a switch to turn it off and probably activate it automatically on 32-bit installations.
Or maybe just keeping most used, or last 42…

EDIT:
I’ve also learned that:

  1. data:url images are part of the HTML - so the page is loaded with ALL images with correct size! Whereas blobs are loaded asynchronously - so initially there are no images and your page layout may change as images are loaded! Also images are being loaded in arbitrary order - which doesn’t look very good.

Now since createObjectURL lifetime is tied to document , there is a crazy idea to create them in background script and that way keeping them cached.

That is what I do, as mentioned with usage reference counting and delayed destruction.
A problem you will likely encounter is that pages that can’t getBackgroundPage() likely also can’t access those blob:-URLs, so you’d still need a fallback for those (or simply avoid/disallow that kind of usage).

You may want to have a look at my private YouTube extension. While it certainly isn’t ready to be released (and may never be), the management of the images as Blobs from the indexedDB to the page works very well. It uses Custom Elements v1(which finally works in FF63, yay!), so from the UI page perspective all I have to do is set the ID and everything else implicitly just works via the lifetime callbacks on the elements.
The interesting files are:
https://github.com/NiklasGollenstede/youtube/blob/master/background/video-info.js
https://github.com/NiklasGollenstede/youtube/blob/master/views/playlist/index.js

Oh man, you are right. They doesn’t work in Private browsing and Firefox Containers :frowning:.

Also after further testing, even with the background cache the initial load is still slower - but only in Firefox, in Chrome it’s amazingly fast.
So I think I will migrate to blobs anyway with hope that Rust-powered Firefox will only get faster over time, fixing this “flashy” page load.