Manifest Version 3 and page action with minimal permissions

How do I write background script code in MV3 (thus non-persistent) in order to always show the page action button without matches, tabs and host permissions?
My usecase is I want to access URL of the current active tab in order to feed into its popup for my extension.

Currently my extension is just a browser action popup with no permissions which just allows pasting a URL and analyze it. Of course the next obvious thing is automatically analyze the current URL in the bar from the click of a button. I could have made it so browser action popup would read the current URL on click and show it instead, but pageAction is obviously more semantically correct place for this.

However I have no idea what kind of perms I need for it work like this:

  • extension still needs no required perms (well except for storage because it’s required for storing settings and cannot be optional) baseline.
  • the page action has to be specifically enabled (with all perms it entails) in order to appear in the URL bar through options.
  • the background script then checks for availability of the setting and then shows the page action button if it’s enabled with pageAction.show().

I assumed the minimal perms required are storage and activeTab, since I don’t want to resort to <all_urls> (even optional) just to read a single URL string. Neither I want tabs for this.

I’ve implemented all the perm and option checking/editing logic so it’s just comes down to checking a setting in the background script, however I have no idea what kind of event I have to listen in order to detect a current tab change:

  • runtime.onStartup doesn’t fit since the current tab can change during the browser session.
  • runtime.onInstalled doesn’t fit since the current tab can change during the lifetime of the extension.
  • runtime.onMessage doesn’t fit since it requires a pre-existing page action/browser action interaction and the hole purpose of this script is to cause page action to show up.
  • runtime.onConnect doesn’t fit since it has the same requirements as runtime.onMessage but for persistent interactions with other parts.

Well, that’s a tough one :slight_smile:.

You could detect a page is loaded with this handler in the background script:

However:

  • it fires only after whole page is loaded (slow pages can load for minutes)
  • it requires webNavigation permission, which will be alerted to users! But can be requested as optional.
  • I’m actually not sure if the URL is present without host permission (or all_urls or tabs permission)

Alternatively, you could use this:

However:

  • to get the URL, you 100% need tabs permission (can be optional)
  • it fires many times per page, so you’ll need to react only to some events

If there is a better way, I don’t know it.
I’ve actually had a very similar case and I’ve ended up with this monstrosity:

// workaround for broken filter in Chrome: https://issues.chromium.org/issues/328143610
browser.tabs.onUpdated.addListener.apply(browser.tabs.onUpdated, $IS_FIREFOX ? [onTabUpdate, {properties: ['status', 'url']}] : [onTabUpdate]);

async function onTabUpdate(tabId: number, changeInfo: browser.tabs._OnUpdatedChangeInfo, tab: browser.tabs.Tab) {
  // console.log('onUpdated', tabId, changeInfo, tab);
  // to get URL asap but also to prevent multiple requests, we react only to first load AND complete load:
  const url = changeInfo.url || (changeInfo.status == 'complete' ? tab.url : '');
  if (!url?.startsWith('http')) return;
  // your code here
}

UPDATE:
I’ve forgot to mention, since MV3 doesn’t have an optional event handlers in the background script (all handlers must be executed sync. top-level), you’ll need to register the handler for everyone, regardless whether they want the feature or not.
Because you can’t synchronously load the users options (actually you can, but only in Firefox and only if you use localStorage).