Content_Scripts optional host permission? (MV2 and MV3)

Is there any way to have a content_script permission that is optional? I don’t want to add a new content script to my add-on and have every user need to accept a new permission when they update (particularly because this permission is google.com and seeing “access your data from google.com” in an update doesn’t seem super great). I’ve tried adding the url to the optional permissions, but the match url from the content script always seems to be added to the required permissions when loading the add-on.

I’ve been attempting to use the scripting.registerContentScripts. It sort of works, but when reloading the add-on, the content_script doesn’t seem to be registered anymore, so I assume that it won’t be registered in normal installs when closing and reopening the browser.

I need something that works in both Manifest V2 and V3 because my add-on is still V2 for Firefox, but V3 for Chrome.

I have one addon where needed exactly this - optionally enabled content scripts (that is, when host permission is granted by the user).

And it can be done, but in two steps:

  1. register the content script (for newly opened tabs)
  2. inject the content script to the existing tabs

I have this code in my addon (for inspiration) running when permissions are granted/removed and when extension first starts (or when browser starts).

  // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/registerContentScripts
  // const scripts = (await browser.scripting.getRegisteredContentScripts()).map(s => s.id);
  await browser.scripting.registerContentScripts([{
    id: domainMatch,
    allFrames: false,
    runAt: 'document_start',
    js: [...getChromiumPolyfill(), '/content_scripts/search_result_previews.cs.js'],
    matches: [domainMatch],
    persistAcrossSessions: $IS_CHROMIUM,    // WARNING: Firefox 102 doesn't support this!
  }])
    .catch(console.error);   // in FF this can fail if the script is already registered (when user uncheck/recheck domain)

  // inject into existing tabs:
  const runningTabs = await browser.tabs.query({url: domainMatch});
  if (runningTabs.length) {
    console.log('injecting CS', runningTabs, domainMatch);
    runningTabs.forEach(t => browser.scripting.executeScript({
      target: {tabId: t.id!, allFrames: false},
      files: [...getChromiumPolyfill(), '/content_scripts/search_result_previews.cs.js'],
    }));
  }
2 Likes

Thanks Juraj! I have it now register the content script when requesting the permission (through the options page in my case), on runtime.onStartup, and on runtime.onInstalled which seems to be good. That should be all that’s needed to keep content script registered, right?

Also curious about the need for the tab injection in your example. Is it only needed to run the script as soon as it’s registered? Because the script should also run if the page is refreshed from what I can tell so I’m not sure if I will bother with that in my add-on.

Well, I have a feeling there is one more (rare) use-case. When the addon is disabled and it’s later on enabled (none of those events will fire).
I’m actually using session store to check if the background script is executing for the first time (by storing there some dummy “addonStarted” value).

If user has already tabs opened, registering script for them doesn’t run it in there, only after they are reloaded.
Also, Firefox was missing the persistAcrossSessions option, so after restarting browser, the session restore could again restore tabs before the content script was registered.

1 Like