Reputation: 21
I want my Chrome extension to only work only on news websites, so I have a long list of many URLs I'd like to limit it to.
Is there an easier way to restrict my extension than manually adding the long list in the "matches"
field of the manifest.json
? Thanks!
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://www.newswebsite.com/*",
"...long long array with urls..."],
...
}
],
...
}
Upvotes: 1
Views: 877
Reputation: 993
I was searching for an answer that worked with the framework, but came to creating straight Javascript
within the Listener
and for the active tab. I also wanted to use a defined function, rather than loading a separate Javascript
file.
My end goal was to have the extension respond to a hotkey, but only for specific hosts. This is how I did it:
In manifest.json
:
...
"commands": {
"some-command": {
"suggested_key": {
"default": "Ctrl+Shift+L"
},
"description": "Tell me something special."
}
},
...
In background.js
:
chrome.commands.onCommand.addListener(function(command) {
chrome.tabs.getSelected(null, function(tab) {
var url = new URL(tab.url);
if (url.hostname != 'somedomain.com') return;
if (command == 'some-command') {
custom_function();
}
});
});
function custom_function {}
The end result is that the extension only works on tabs that have the particular domain name, regardless if triggering a hotkey command or through the address bar popup. I didn't include the address bar popup code, as that is right on the "build a Chrome Extension" guide - something I'd expect we have all already completed.
Upvotes: 0
Reputation: 69296
I'll just start by saying that well, the "simplest" way to inject your content scripts in all the sites of your list is to actually just add them in the array of "matches"
in the extension's manifest.
If you are worrying about having to insert each site in the array manually then that's no big deal, you can easily paste it inside a text editor and use find and replace to do what you want, for example replacing \n
with /*",\n"*://
and then editing the first and last manually:
site1.com -> site1.com/*", -> "*://site1.com/*",
site2.net -> "*://site2.net/*", -> "*://site2.net/*",
site3.org -> "*://site3.org/*", -> "*://site3.org/*"
"*//
Once you've got this you can just copy and paste it inside your "matches": [ ...
array
tabs
APIIf you don't really want to add them inside your manifest, you can put them in a text file inside your extension's directory, then load it in your background page and add a listener to chrome.tabs.onUpdated
to check when the URL changes and inject the script if the new URL matches one of the sites. This IMHO is more complicated than simply adding them in your manifest.
Working example
Your list.txt
:
site1.com
site2.net
site3.org
Your background.js
script:
// Function to extract the hostname from an URL.
function getHostname(url) {
return url.match(/^(.*:)\/\/([A-Za-z0-9\-\.]+)/)[2];
}
// XHR to get the list.txt
var xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState == 4 && xhr.status == 200) {
// Parse the list and create a set from it:
var list = new Set(xhr.responseText.split('\n'));
// Listen for tabs.onUpdated:
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
// If the tab changed URL:
if (changeInfo.url)
// If the new URL is one of the sites in the list:
if (list.has(getHostname(changeInfo.url)))
// Inject the script:
chrome.tabs.executeScript(tabId, {file: 'yourScript.js'});
});
}
});
xhr.open('GET', 'list.txt', true);
xhr.send();
Don't forget to also add permissions for <all_urls>
(since you're gonna inject scripts in sites not listed directly), and the chrome.tabs
API in your manifest:
...
"permissions": ["<all_urls>", "tabs"],
...
declarativeContent
APISimilar to the previous one, but simpler: parse the list.txt
and create a new rule to be processed by the chrome.declarativeContent
API. You will not have to worry about matching the URLs manually, and Chrome will run the matches and optimize the rule for you.
chrome.runtime.onInstalled.addListener(function() {
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
var xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var list = xhr.responseText.split('\n');
// Create a new rule with an array of conditions to match all the sites:
var rule = {
conditions: list.map(site => new chrome.declarativeContent.PageStateMatcher({
pageUrl: { hostEquals: site, schemes: ['http', 'https'] }
})),
// To inject the script when the conditions are met:
actions: [new chrome.declarativeContent.RequestContentScript({js: ['yourScript.js']})]
}
// And register it:
chrome.declarativeContent.onPageChanged.addRules([rule]);
}
});
});
xhr.open('GET', 'list.txt', true);
xhr.send();
});
Note that the above code is all wrapped inside a chrome.runtime.onInstalled
listener, since that declarativeContent
rules are persistent, and you don't need to add them each time your extension starts.
You'll need permission for both "<all_urls>"
and "declarativeContent"
in your manifest in this case:
...
"permissions": ["<all_urls>", "declarativeContent"],
...
Now, with this said, I actually think that the easiest way to do what you want is to just simply add the sites in your "matches"
field of the extension's manifest, but you're free to do what you think is best. I do actually use the second approach often, however overall the third method is the most efficent if you don't want to manually add the matching rules in your manifest.json
.
Take a look at the documentation for the methods and types I used in the examples if you want to know more:
chrome.tabs.onUpdated
chrome.tabs.executeScript
chrome.declarativeContent.PageStateMatcher
chrome.declarativeContent.RequestContentScript
Upvotes: 2