Runtime messaging performance to sandbox

176 views
Skip to first unread message

Todd Schiller

unread,
Jun 25, 2025, 8:34:19 AM6/25/25
to Chromium Extensions
In our extension, we support execution of text templates via sandboxed iframe. 

For reliability across host sites, we've embedded the iframe in the Offscreen Document. (Host sites/frames could interfere even when we wrapped the sandboxed iframe in ShadowDOM).

The messaging flow looks like:

Content Script -> Background Worker -> Offscreen Document -> Sandbox IFrame

This generally works OK, but in low-resource environments we've been running into performance/memory problems for larger payloads, e.g., 10KB+

Our understanding is that Chrome uses the following serialization approaches:
  • runtime.sendMessage (for background worker/offscreen): uses JSON-serialization
  • window.postMessage (for sandbox): uses structuredClone
Given the multiple serialization/deserialization, should it be more performant to have the content script JSON-ify the payload before sending it? 

In early benchmarks, it seems like pre JSON-ification improves peak memory use. But would be interested in what the Chrome team/other extension developers would recommend

Thanks,
Todd


woxxom

unread,
Jun 26, 2025, 1:44:38 AM6/26/25
to Chromium Extensions, Todd Schiller
Indeed, for complex objects, pre-serializing is beneficial because a string is much easier to encode/decode.

However, you can use web platform messaging to send messages directly without a relay by creating a secure iframe in the web page temporarily just to send a MessagePort to your sandboxed iframe in the offscreen document, then delete the iframe, and use the port directly from the content script. Use "Web messaging (two-way MessagePort)" in https://stackoverflow.com/a/68689866 and modify the background portion like this:

// iframe.js (web_accessible_resources) **********************************************************

let port;
window.onmessage = e => {
  if (e.data === new URLSearchParams(location.search).get('secret')) {
    window.onmessage = null;
    navigator.serviceWorker.ready.then(swr => {
      swr.active.postMessage('port', [e.ports[0]]);
    });
  }
};

// background.js **********************************************************

self.onmessage = async e => {
  if (e.data === 'port') {
    (await getOffscreenClient()).postMessage('port', [e.ports[0]]);
    e.ports[0].onmessage = onContentMessage;
    e.ports[0].postMessage(null); // resolve makeExtensionFramePort
  }
}
function getOffscreenClient(e) {
  const all = await clients.matchAll({includeUncontrolled: true, type: 'window'});
  return all.find(a => a.url.includes('offscreen.html'))
    || createOffscreen().then(getOffscreenClient);
}
async function createOffscreen() {
  try {
    await chrome.offscreen.createDocument({
      url: 'offscreen.html',
      reasons: ['BLOBS'],
      justification: '...',
    });
  } catch (err) {
    if (!err.message.startsWith('Only a single offscreen')) throw err;
  }
}

// offscreen.js **********************************************************

var iframeElem;
self.onmessage = e => {
  if (e.data === 'port') {
    iframeElem.contentWindow.postMessage('port', [e.ports[0]]);
  }
}

// sandboxed-iframe.js **********************************************************

var contentPort;
self.onmessage = e => {
  if (e.data === 'port') {
    contentPort = e.ports[0];
    contentPort.onmessage = onContentMessage;
    contentPort.postMessage(null); // resolve makeExtensionFramePort
  }
}
function onContentMessage(e) {
  console.log('from content:', e.data);
  port.postMessage('ok');
}

Reply all
Reply to author
Forward
0 new messages