How to figure out when injecting a content script would fail?

Currently I’m developing an addon which conditionally shows a page action when it detects it could be useful based on some basic tab state. However, currently it may show itself on pages that it can’t inject scripts into (all the exceptions to activeTab). I’m wondering if there’s a reliable way to preemptively check if broswer.tabs.executeScript() would fail if invoked with a given tab ID, since I’d rather not show the page action if it would fail to inject its script.

By calling executeScript and checking what you get back.

So there’s no way to do it without actually trying to execute a script? At the time I’m doing this check, I’m not ready to actually run the script yet and, unless I request permission to inject scripts into all URLs (currently I only request activeTab since that’s all I really need otherwise), I wouldn’t have the right permissions to do it.

Requesting the host permission for <all_urls> just to check if a tab is at a 100% non-injectable URL seems kind of excessive.

In almost all cases, tabs.executeScript() should work normally, unless browser is hard-coded to prevent it.

For example Chrome prevents tabs.executeScript() on some Google sites like Play Store.

Firefox is also hard-coded to prevent tabs.executeScript() running on some pages like AMO.

Additionally, internal pages such as about:* etc will not be available to tabs.executeScript()

The list of these sites are quite small and can be checked before showing Page Action using "show_matches" or "hide_matches" and/or pageAction.show() or pageAction.hide()

Unfortunately, neither of these options are as robust as I’d like.

"show_matches": ["<all_urls>"] will hide the page action on the about: pages, but not blacklisted sites like AMO.

Similarly, the show() and hide() methods on pageAction don’t prevent me from displaying the page action on any of these pages (which I’d expect, since there’s some value in displaying page actions even on pages that disallow tabs.executeScript()).

It’s looking like I might need a manually maintained blacklist to do what I want, at least with current APIs.

Why not?

"page_action": {
  "show_matches": ["<all_urls>"],
  "hide_matches": ["https://addons.mozilla.org/*"]
}

Again … why not? (for add-ons that programmatically processes pageAction)

I have used both methods in different add-ons to control Page Action on AMO and other sites.

Of course I can always manually control this (and that is the solution I ended up going with), but I’d much rather the browser tell me what I’m not allowed to do rather than manually maintaining a blacklist. It’s a fragile approach that requires action on my side if the blocked domains/protocols ever change.

There are only 15 sub-domains of mozilla.org/net/com & firefox.com that are blocked and normal users will only come across 2-3 of them.

Personally, I have only had to take AMO into consideration and the rest have not been an issue.

Things are going to get interesting when users can override the list by editing the extensions.webextensions.restrictedDomains preference. They could clear Mozilla domains, but might they also add domains? That seems unlikely in a consumer context but might be more relevant in a managed environment.

This is exactly the sort of concern I have with hardcoding a blacklist. Even if I went out of my way to go and copy all the currently restricted domains from all the browsers, that seems very likely to drift out of sync. It’d be better if there was a way for me to just ask the browser if a given URL was off-limits.

Yes. I think there is a place to propose new APIs, but I can’t recall where you do that. It comes up from time to time on this mailing list:

https://mail.mozilla.org/listinfo/dev-addons

As a short-term workaround, can you test using executeScript with a short script that doesn’t do anything?

Not really. Or rather, not without requesting otherwise unnecessary permissions. Currently I use activeTab, but the check happens in the background to decide whether I should let the user try to interact with the tab in the first place.

I’ve got a blacklist in place as a temporary work around, but I will definitely look into proposing a new API for this.

If this method does not take into account restrictions (I have not tested myself), it definitely would be a good place to add them because otherwise… it’s just parsing strings not providing the answer you need:

Hmm, I had discounted that API initially because I was using activeTab but I suppose it’s worth a try. I’ll run some tests with that later today.

So it seems that, as I initially, suspected, permissions.contains() doesn’t really handle activeTab in a useful way. I’m surprised that it even doesn’t seem to respect retricted domains.

With <all_urls> in my manifest:

  • returns true for non-restricted domain
  • still returns true for restricted domain
  • returns false for an about: page, so that’s something I guess

With only activeTab in my manifest

  • returns false regardless of url I pass in for origins
  • still returns false even if I run it in a context where I am allowed to modify the active tab

So yeah, definitely some room for improvement there.

Having activeTab does by far not grant you the host permission. You still don’t have special powers related to things like webRequest or tracking protection (you can circumvent CORS with a bit of work though, but not as simple as with a host permission).

Be that as it may, it doesn’t really solve the core issue that the API as it currently exists doesn’t really give me enough information to intelligently handle restricted domains, regardless of if I’m using activeTab or have full host permissions.

I’ll keep that in mind though if I end up submitting an API request.

There is a function in beta (Firefox 60) and nightly (Firefox 61) to check whether an address is restricted for WebExtensions:

WebExtensionPolicy::IsRestrictedURI()

So perhaps that could be exposed through the API for querying.

But I suspect it might be more efficient to get the full list (value of the preference) and do the comparison in your own script rather than making API calls for every page, since this preference is unlikely to change frequently.