chrome extension activeTab permission to executeScript in frames - google-chrome-extension

I am trying to avoid using the <all_urls> permission since I theoretically only need the activeTab permission, especially to avoid waiting one week every time Google has to review my extension's new releases.
However, it seems that I cannot use chrome.tabs.executeScript() directly into the tabs' frames without the <all_urls> permission.
// background.js
chrome.tabs.executeScript(
{
frameId: frame.frameId,
code: `
var videoTag = document.getElementsByTagName("video");
console.log("videotag:", videoTag)
if (videoTag.length > 0) {
var title = document.getElementsByTagName("title")[0].text;
var ok = true;
[ok, title];
}`
}
)
The code above never gets executed with only activeTab permission specified, but works well with <all_urls> permission.
Is the activeTab permission completely useless when it comes to access cross-origin frames in the "active tab"? In that case, do I have any other solution to achieve the same goal without using the <all_urls> permission?
Furthermore, since I do not specify the tabId in which I want to execute the script, this implicitly means that I want to execute it in the "active tab":
integer - (optional) tabId - The ID of the tab in which to run the script; defaults to the active tab of the current window.
Source: https://developer.chrome.com/extensions/tabs#method-executeScript

activeTab doesn't just mean "whatever tab is on screen, you have access to it"
A tab becomes "active" when the user invokes your extension when they're using it. Refer to the documentation for activeTab
The following user gestures enable activeTab:
Executing a browser action
Executing a page action
Executing a context menu item
Executing a keyboard shortcut from the commands API
Accepting a suggestion from the omnibox API

Related

Chrome extension: How to inject HTML into any tab w/o using <all_urls>

I am trying to inject a content script to the current tab of the user once the timer is done. Thus, my manifest is:
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["jquery-3.5.1.min.js", "flipclock-min.js", "content.js"],
"css": ["normalize.min.css", "styles2.css", "flipclock.css", "css.css"]
}]
According to web store publishing process:
Broad Host Permissions
Instead of requesting broad host permissions, consider using the
activeTab permission, or specify the sites that your extension needs
access to. Both options are more secure than allowing full access to
an indeterminate number of sites, and they may help to minimise review
times.
The activeTab permission allows access to a tab in response to an
explicit user gesture.
{
...
"permissions": ["activeTab"]
}
It makes sense for me to use activeTab instead. However, as they mentioned, I can use it in permission not content_scripts or else I would get:
Invalid value for 'content_scripts[0].matches[1]': Missing scheme separator.
How should I go about this?
activeTab won't work on timer fully automatically because it needs an explicit user action: the user must click your extension icon (or interact via a commands API hotkey, a contextMenus API menu, or an omnibox API keyword, more info in the documentation) to start or set the timer.
Through such user action activeTab will grant access to the current tab so your background script can use chrome.tabs.executeScript to inject the scripts (use one call per each script).
And, remove content_scripts in manifest.json.

how to click cancel in prompt box?

I am developing a chrome extension to suppress a prompt box.
The access is protected in server side and user is triggered with prompt box for username/password as shown below,
I am injecting a content_script in the url at document_start and trying to detect the presence of this prompt, if present "cancel" button need to be clicked.
Here is a test link to get the login prompt,
http://128.199.223.179/testing/test.php
You technically can't close the prompt only using the javascript api... You might need to use an NPAPI plugin or other external software such as Selenium to emulate a click on the close button.
But, what you can do is prevent that dialog from popping up in the first place by intercepting an authentication request.
First add "webRequest" permission in your manifest. Then try this code:
// to listen to all urls use { urls: ["<all_urls>"] }
chrome.webRequest.onAuthRequired.addListener(function(details) {
return {cancel: true};
}, {urls: ["http://128.199.223.179/testing/test.php"]}, ["blocking"]);
For better documentation, visit https://developer.chrome.com/extensions/webRequest#event-onAuthRequired

Optionally inject Content Script

Content Script can be injected programatically or permanently by declaring in Extension manifest file. Programatic injection require host permission, which is generally grant by browser or page action.
In my use case, I want to inject gmail, outlook.com and yahoo mail web site without user action. I can do by declaring all of them manifest, but by doing so require all data access to those account. Some use may want to grant only outlook.com, but not gmail. Programatic injection does not work because I need to know when to inject. Using tabs permission is also require another permission.
Is there any good way to optionally inject web site?
You cannot run code on a site without the appropriate permissions. Fortunately, you can add the host permissions to optional_permissions in the manifest file to declare them optional and still allow the extension to use them.
In response to a user gesture, you can use chrome.permission.request to request additional permissions. This API can only be used in extension pages (background page, popup page, options page, ...). As of Chrome 36.0.1957.0, the required user gesture also carries over from content scripts, so if you want to, you could add a click event listener from a content script and use chrome.runtime.sendMessage to send the request to the background page, which in turn calls chrome.permissions.request.
Optional code execution in tabs
After obtaining the host permissions (optional or mandatory), you have to somehow inject the content script (or CSS style) in the matching pages. There are a few options, in order of my preference:
Use the chrome.declarativeContent.RequestContentScript action to insert a content script in the page. Read the documentation if you want to learn how to use this API.
Use the webNavigation API (e.g. chrome.webNavigation.onCommitted) to detect when the user has navigated to the page, then use chrome.tabs.executeScript to insert the content script in the tab (or chrome.tabs.insertCSS to insert styles).
Use the tabs API (chrome.tabs.onUpdated) to detect that a page might have changed, and insert a content script in the page using chrome.tabs.executeScript.
I strongly recommend option 1, because it was specifically designed for this use case. Note: This API was added in Chrome 38, but only worked with optional permissions since Chrome 39. Despite the "WARNING: This action is still experimental and is not supported on stable builds of Chrome." in the documentation, the API is actually supported on stable. Initially the idea was to wait for a review before publishing the API on stable, but that review never came and so now this API has been working fine for almost two years.
The second and third options are similar. The difference between the two is that using the webNavigation API adds an additional permission warning ("Read your browsing history"). For this warning, you get an API that can efficiently filter the navigations, so the number of chrome.tabs.executeScript calls can be minimized.
If you don't want to put this extra permission warning in your permission dialog, then you could blindly try to inject on every tab. If your extension has the permission, then the injection will succeed. Otherwise, it fails. This doesn't sound very efficient, and it is not... ...on the bright side, this method does not require any additional permissions.
By using either of the latter two methods, your content script must be designed in such a way that it can handle multiple insertions (e.g. with a guard). Inserting in frames is also supported (allFrames:true), but only if your extension is allowed to access the tab's URL (or the frame's URL if frameId is set).
I advise against using declarativeContent APIs because they're deprecated and buggy with CSS, as described by the last comment on https://bugs.chromium.org/p/chromium/issues/detail?id=708115.
Use the new content script registration APIs instead. Here's what you need, in two parts:
Programmatic script injection
There's a new contentScripts.register() API which can programmatically register content scripts and they'll be loaded exactly like content_scripts defined in the manifest:
browser.contentScripts.register({
matches: ['https://your-dynamic-domain.example.com/*'],
js: [{file: 'content.js'}]
});
This API is only available in Firefox but there's a Chrome polyfill you can use. If you're using Manifest v3, there's the native chrome.scripting.registerContentScript which does the same thing but slightly differently.
Acquiring new permissions
By using chrome.permissions.request you can add new domains on which you can inject content scripts. An example would be:
// In a content script or options page
document.querySelector('button').addEventListener('click', () => {
chrome.permissions.request({
origins: ['https://your-dynamic-domain.example.com/*']
}, granted => {
if (granted) {
/* Use contentScripts.register */
}
});
});
And you'll have to add optional_permissions in your manifest.json to allow new origins to be requested:
{
"optional_permissions": [
"*://*/*"
]
}
In Manifest v3 this property was renamed to optional_host_permissions.
I also wrote some tools to further simplify this for you and for the end user, such as
webext-domain-permission-toggle and webext-dynamic-content-scripts. They will automatically register your scripts in the next browser launches and allow the user the remove the new permissions and scripts.
Since the existing answer is now a few years old, optional injection is now much easier and is described here. It says that to inject a new file conditionally, you can use the following code:
// The lines I have commented are in the documentation, but the uncommented
// lines are the important part
//chrome.runtime.onMessage.addListener((message, callback) => {
// if (message == “runContentScript”){
chrome.tabs.executeScript({
file: 'contentScript.js'
});
// }
//});
You will need the Active Tab Permission to do this.

Let user add a content_script match in a Chrome extension

I would like to allow users to click on a "use the extension for this website" button.
It would allow me to don't have matches for my content script at first and add them when the user needs it, is it possible? When a user clicks on the button, the extension will do the job every time the website is visited, not just once.
While possible (see the second pat of the question), it might be slightly hard to use.
A practical solution would be to declare a content script that is always injected (i.e. <all_urls> match pattern), but abort execution if the page doesn't match:
// content.js
// Maybe an async check to background / chrome.storage
if(someCondtion(window.location.host)) startWorking();
function startWorking() {
/* all your main code here */
}
This will cause a scarier permission warning at runtime, but is probably the easiest to implement.
Chrome has a mechanism called "optional permissions" that you can request after install time. You can request a blanket host permission as optional, and then only request origins you need.
Warning, the code below was not tested.
// background.js
function requestHostPermission(host){
chrome.permissions.request({
origins: ['*://' + host + '/']
}, function(granted) {
// The callback argument will be true if the user granted the permissions.
if (!granted) {
if(chrome.runtime.lastError) console.error(chrome.runtime.lastError);
throw Error("Permission denied for host "+host);
}
});
}
But then you will have to use some kind of programmatic injection, as you cannot use optional permissions with manifest-declared scripts.

chrome.tabs.executeScript not working?

I am trying to learn to use the chrome.tabs.executeScript commend. I've created a simple extension with a browser action. My background.html file currently looks like this:
<html>
<script>
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(null,{code:"document.body.bgColor='red'"});
chrome.tabs.executeScript(null, {file: "content_script.js"});
});
</script>
</html>
The "content_script.js" file contains document.body.bgColor='red'.
When pushing the browser action's button nothing happens. Obviously I'm missing something very basic.
I've checked with console.log that indeed control reaches the chrome.tabs.executeScript calls when the browser action is pressed. Otherwise I'm not sure how to even check if my content script's code is run (it seems not; console.log I put in the content script has no effect, but maybe it shouldn't have one even if the script is run successfully).
Make sure you have domain and tab permissions in the manifest:
"permissions": [
"tabs", "http://*/*", "https://*/*"
]
Then to change body color try:
chrome.tabs.executeScript(null,{code:"document.body.style.backgroundColor='red'"});
Also keep in mind that content scripts are not injected into any chrome:// or extension gallery pages.
For those of you still having issues, you need to make sure to reload the extension's permissions in Chrome.
Go to chrome://extensions , scroll to your extension, and click on "reload". Make sure that your permissions have been updated by clicking on the permissions link right next to your extension.
You actually don't need and don't want the 'tabs' permission for executeScript.
"permissions": [
"http://*/*",
"https://*/*"
]
Should be enough.
It's not recommended to use http://*/* and https://*/*. From the Google documentation:
To inject a programmatic content script, provide the activeTab permission in the manifest. This grants secure access to the active site's host and temporary access to the tabs permission, enabling the content script to run on the current active tab without specifying cross-origin permissions.
Instead, (as suggested in the page) just use activeTab permission.
Remark: more explanation for the security issue
Without activeTab, this extension would need to request full, persistent access to every web site, just so that it could do its work if it happened to be called upon by the user. This is a lot of power to entrust to such a simple extension. And if the extension is ever compromised, the attacker gets access to everything the extension had.
In contrast, an extension with the activeTab permission only obtains access to a tab in response to an explicit user gesture. If the extension is compromised the attacker would need to wait for the user to invoke the extension before obtaining access. And that access only lasts until the tab is navigated or is closed.
(emphasis mine)
In the example code posted by the OP, activeTab is sufficient.
However, if the extension is more complex and needs to work "automatically" (i.e. without the user clicking the button); then this method will not work and additional permission is required.
Most of the answers above seems to be working fine for manifest version 2 but when it comes manifest-3 their seems to be some workaround to make the content-script load in the latest manifest 3.We need to use the following steps to execute content script in manifest 3
First adding permission "scripting" in manifest
"permissions": [
"storage",
"tabs",
"activeTab",
"scripting"
]
Once the scripting perimission is provided, we can use the scripting api like below
In background.js,
chrome.tabs.query({}, (tabList) => {
if (!tabList.length) return;
tabList.forEach((tab) => {
chrome.scripting.executeScript(
{
files: ['contentScript.js'],
target: {
tabId: tab.id,
allFrames: true
}
}
);
});
});
In the above code we are executing the contentScript for all the available tabs in tab browser.

Resources