Is "chrome.offscreen" ready for production? (in the long term)

78 views
Skip to first unread message

Juraj M.

unread,
Dec 3, 2024, 7:07:06 AM (yesterday) Dec 3
to Chromium Extensions
I'm gonna be super honest here.
Looking the offscreen API and it's usages, I can't shake the feeling that this is just all wrong, and nobody in their right mind would use such a terrible workaround:

The amount of code and effort I need to put just to perform some "querySelector" is insane.

If there is a deprecation list for the Manifest V4, the offscreen document API is 100% already there.

I'm complaining because this is like the 3rd time I want to use it, and when see how, I'm like - no way this is real.

Maybe there are some polyfills that can abstract all that nonsense?

woxxom

unread,
Dec 3, 2024, 6:13:04 PM (22 hours ago) Dec 3
to Chromium Extensions, Juraj M.
The examples are unnecessarily complicated and still fragile in case another context is using the API concurrently. There's no need for all that, you can simply create the document and ignore the API error if it already existed: https://stackoverflow.com/a/77258394

You can also use the more efficient web platform messaging to pass binary data: https://stackoverflow.com/a/77427098

Anyway I feel your pain. It's not surprising the API design is so clunky as it was created by a C++ programmer who thinks in C++ paradigms where such API is quite reasonable due to compiler optimizations of the abstractions. Other developers didn't see the need to even challenge this design,  maybe because everyone was impressed by how mature and serious this API looked (and no, it didn't) or because they thought it's a temporary stopgap that will be used until the missing stuff is implemented in a service worker soon. Like, in 20 years, I suppose, because the service worker feature doesn't need those changes, it only caters to the web platform's needs and as such was always the worst possible choice for an extension's background script (this is yet another mistake by chromium developers due to their lack of experience in the practical aspects of extension development).

A better API might have been something like chrome.scripting.offscreen ("scripting" permission) or chrome.offscreen.executeScript ("offscreen" permission):

const exampleFunc = await chrome.offscreen.executeScript({func: funcToRun, args: ['foo']});
const exampleFiles = await chrome.offscreen.executeScript({files: ['foo.js', 'bar.js']});

1. the document is created automatically or reused if it's already running
2. no condescending "justification" and "reasons"
3. "func" and "args" from executeScript i.e. it can be async or Promise-based

Looks like any other sensible API, simple and self-contained. It could also allow automatic unloading of the document after the code runs, using the standard sliding timeout for the event page or a service worker. It could also include a "timeout" parameter. Realistically though I don't expect this API to change anytime soon if ever.

woxxom

unread,
Dec 3, 2024, 7:25:06 PM (20 hours ago) Dec 3
to Chromium Extensions, woxxom, Juraj M.
I'd also like to note the similarly over-complicated official onMessage inside the offscreen document that uses several anti-patterns: async listener is not supported by the API so instead of handling it properly via "return true" they used a separate sendMessage which requires a separate onMessage in the initiator's context, which means that it doesn't scale to more complicated use cases where the original context's data should be used with the response and the only "solution" would be to complicate even further and introduce a global map for message contexts.

The proper solution is to use sendMessage + sendResponse + return true because it's both simpler and more functional as it receives the response in the original context.

offscreen.js:

chrome.runtime.onMessage.addListener(({cmd, data}, sender, sendResponse) => {
  if (cmd === 'add-exclamationmarks-to-headings') {
    sendResponse(addExclamationMarksToHeadings(data));
  }
});

function addExclamationMarksToHeadings(str) {
  const parser = new DOMParser();
  const doc = parser.parseFromString(str, 'text/html');
  for (const el of doc.querySelectorAll('h1')) el.append('!!!');
  return doc.documentElement.outerHTML;
}

background.js:

chrome.action.onClicked.addListener(async () => {
  const res = await offscreen(
    'add-exclamationmarks-to-headings',
    '<html><head></head><body><h1>Hello World</h1></body></html>'
  );
  console.log(res);
});

async function offscreen(cmd, data) {
  try {
    await chrome.offscreen.createDocument({
      url: 'offscreen.html',
      reasons: ['DOM_PARSER'],
      justification: 'MV3 requirement',
    });
  } catch (error) {
    if (!error.message.startsWith('Only a single offscreen'))
      throw error;
  }
  return chrome.runtime.sendMessage({cmd, data});
}

P.S. You may also want to surface the errors from the offscreen document:

// offscreen.js
chrome.runtime.onMessage.addListener(({cmd, data}, sender, sendResponse) => {
  let result, error;
  try {
    if (cmd === 'add-exclamationmarks-to-headings') {
      result = addExclamationMarksToHeadings(data);
    } else throw new Error('Unknown command ' + cmd);
  } catch (err) {
    error = err;
  }
  if (result instanceof Promise) {
    result.then(res => sendResponse({result: res}), err => sendResponse({error: [err.message, err.stack]}));
    return true;
  }
  sendResponse({result, error});
});

// background.js
async function offscreen(cmd, data) {
  try {
    await chrome.offscreen.createDocument({
      url: 'offscreen.html',
      reasons: ['DOM_PARSER'],
      justification: 'MV3 requirement',
    });
  } catch (error) {
    if (!error.message.startsWith('Only a single offscreen'))
      throw error;
  }
  const {result, error} = await chrome.runtime.sendMessage({cmd, data});
  if (error) throw Object.assign(new Error(error[0]), {stack: error[1]});
  return result;
}

Juraj M.

unread,
7:38 AM (8 hours ago) 7:38 AM
to Chromium Extensions, woxxom, Juraj M.
OMG! Exactly, the offscreen.executeScript would be such a great help! 
How come nobody think of that before? And let it up to developers to handle the document creation/state/stupid messaging and zero API, not even storage!
Did you requested it already in the bugracker? I would like to star that!

This is also good time to mention that Chrome still doesn't support returning Promise in the runtime.onMessage handler to send a response:
https://issues.chromium.org/issues/40753031

And the fact that we still can't send blobs is also pretty annoying - in general, not just for offscreen:
https://issues.chromium.org/issues/40321352

Thank you for the useful examples and intelligent insights! It feels great to finally read some intelligent content! :)

woxxom

unread,
8:17 AM (8 hours ago) 8:17 AM
to Chromium Extensions, Juraj M., woxxom
I didn't think of this API earlier (other than noting its clumsiness) and just came up with this solution. I'm reluctant to suggest it in the bug tracker though as I'm overly pessimistic on this matter, so it'd be nice if you do it instead.

Juraj M.

unread,
1:58 PM (2 hours ago) 1:58 PM
to Chromium Extensions, woxxom, Juraj M.
Oh man, let me tell you a funny story.

When I studied at the University 10+ years ago, one classmate once told me, that he is using some 3rd party library for his thesis, and that he found a bug in it, and reported it to the author. 
And at that moment (imagine me - who never used a 3rd party library before) it sounded so cool, like that he must be so smart, that he can actually find a bug in a "professional" 3rd party library! :D.

That's it - it was suppose to be funny, because finding and reporting bugs is now one of my least favorite thing to do, and I can't believe I admired him for that :), and that I thought it's "cool" to find bugs in software.

Thanks again for the idea!
Reply all
Reply to author
Forward
0 new messages