Is there a way to await IFRAME render operation?

231 views
Skip to first unread message

Juraj M.

unread,
May 2, 2024, 5:02:00 AM5/2/24
to Chromium Extensions
I'm trying to fix a race condition - an iframe is blank (even when page inside is fully loaded) and `tabs.captureVisibleTab` will capture fully blank screenshot.

This happens because I load the extension page as inactive pinned tab. And even though the iframe inside will load the page, the iframe itself won't render the content unless the tab is visible "for some time".

I want to replace the "for some time" with a deterministic operation that would guarantee the IFRAME content to draw.
I've tried to call "requestAnimationFrame" (twice in a series), also inject content script that calls it inside the iframe page:
// request animation frame from this tab and all iframes
async function requestAllAnimationFrames() {
  await requestAnimationFramePromise();
  const tabId = await currentTabIdPromise;
  const frames = await browser.webNavigation.getAllFrames({tabId: tabId}).catch(() => [{frameId: 0}]);
  const results = await Promise.all(frames.map(frame => browser.scripting.executeScript({
  injectImmediately: true,
  target: {tabId, frameIds: [frame.frameId]},
  // requesting two frames
  func: () => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))),
  })));
  iLog('all frames requested animation frame', results);
}

And even though all this code runs after the tab is focused and fully visible, the iframe won't render in time if CPU is slow or page too complex or additional delay is not big enough.

Is there anything else I can execute in the tab or in the iframe page that can be awaited and will cause the content to show?

wOxxOm

unread,
May 2, 2024, 8:17:17 AM5/2/24
to Chromium Extensions, Juraj M.
AFAIK, Chrome may intentionally skip rendering invisible iframes, especially in inactive tabs, in which case there's no solution.

Another possibility is that the site uses requestAnimationFrame, which runs only in visible pages. In this case you can inject a content script in the MAIN world at document_start:

if (document.visibilityState !== 'visible') {
  const raf = requestAnimationFrame;
  const st = window.requestAnimationFrame = setTimeout;
  document.addEventListener('visibilitychange', () => {
    if (requestAnimationFrame === st) window.requestAnimationFrame = raf;
  }, {once: true});
}

The site may explicitly detect visibility though, in which case you'll have to spoof whatever it uses e.g. document.visibilityState.

Juraj M.

unread,
May 2, 2024, 8:28:04 AM5/2/24
to Chromium Extensions, wOxxOm, Juraj M.
It happens basically with all sites, it's really only the iframe being not rendered to improve performance, which is great.
I just need a "callback" that will tell me "render complete, you can take a screenshot now". I've tried to change layout on the tab, change CSS to resize iframe, but they seem to run independently to iframe "render loop".

wOxxOm

unread,
May 2, 2024, 8:31:53 AM5/2/24
to Chromium Extensions, Juraj M., wOxxOm
Try waiting for load event inside frameId e.g. func: () => new Promise(resolve => addEventListener('load', resolve, {once: true}))
BTW you can specify multiple frameIds in one executeScript instead of making a separate call per frame.

Juraj M.

unread,
May 2, 2024, 8:45:50 AM5/2/24
to Chromium Extensions, wOxxOm, Juraj M.
Good idea with the load event! But that should not be needed in this case - I'm currently using "webRequest.onBeforeRequest" and "webRequest.onCompleted" to track all requests in the frame and I'll resolve it only once all requests are done - since many pages will load additional content so the screenshot would be incomplete.
Regarding the multiple frameIds, if I remember correctly, there was an issue that the whole promise was rejected if one of the iframes could not be injected (when it loaded some problematic page, like extension page, error page, protected page...). I'm not sure anymore, it was long ago, but just in case I keep it as it is.
Making thumbnails of pages in my extension is one of the most troublesome things I've ever programmed, so many special cases and browser specific issues :(

wOxxOm

unread,
May 2, 2024, 8:52:17 AM5/2/24
to Chromium Extensions, Juraj M., wOxxOm
> But that should not be needed in this case - I'm currently using "webRequest.onBeforeRequest" and "webRequest.onCompleted"

I think it's needed because after a request is completed it's not shown immediately, there's a lot of work to be done inside the page to render it.
Reply all
Reply to author
Forward
0 new messages