Problem
I am making a Chrome extension that downloads files and adds links to these downloaded files to a webpage. When these links are clicked, I would like to relay the click as a "user gesture" to my background script so that the file opens without prompting. Looking at the docs on the relevant method, chrome.downloads.open, there is no discussion of user gestures.
Essentially, I want to get rid of this =>
using the idea in this comment.
Background
It seems like this is possible because
This post on what constitutes a user gesture lists click as one of the types of user gestures
The spec, which says clicks will generate a user gesture
In the code below, logging the event results in a MouseEvent, with type click and isTrusted set to true.
[downloads.open] can only run in a code initiated by a user action, like a click on a button. It cannot be executed from non-user events. - Xan, comment for How to open a downloaded file?
Code below aims to be an MCVE.
Content Script
// Add an event listener for every download link
function addDownloadListeners() {
const pathElems = document.getElementsByClassName('pathClass');
for (path of pathElems) {
path.addEventListener('click', openDownload);
}
}
// Send a message with the download ID to the background script
function openDownload(event) {
const selector = '#' + event.currentTarget.id;
const downloadId = parseInt($(selector).attr('download_id'));
chrome.runtime.sendMessage({
'downloadId': downloadId,
}, function(response) {
if (response !== undefined) {
resolve(response.response);
} else {
reject(new Error(response.response));
}
});
}
manifest.json
{
"background": {
"scripts": ["js/background.js"]
},
"content_scripts": [
{
"js": [
"js/content_script.js"
],
"matches": ["*://*.website.com/*/urlpath*"],
"run_at": "document_end"
}
],
"permissions": [
"downloads",
"downloads.open"
],
"manifest_version": 2,
}
Background Script
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
try {
chrome.downloads.open(request.downloadId);
} catch (e) {
sendResponse({response: 'Error opening file with download id ' +
request.downloadId + ' getting error ' + e.toString()
});
}
}
)
Question
How can I get a click to open a download without creating an additional prompt?
This is not possible
It is not possible to prevent/use an alternative to prompts for chrome methods that require user consent. Based on this discussion in the Chromium Extension google group,
Some Chrome methods (like chrome.downloads.open) need to get user consent via prompt.
These are automatically generated via Chrome itself - they cannot be overridden or modified.
User gestures outside of prompts are not relevant to methods that require user consent.
This current behavior is circa 2014 for chrome.downloads.open.
Special thanks to #wOxxOm and Decklin Johnston for making this answer possible.
I am trying to execute a script that shows a green border on the specified tab (by ID). The script should execute when the response for the requested URL is an error. The problem is that, when I load the extension from about:debugging, I get the following error (in the browser console in FF 53):
Error: No window matching {“matchesHost”:[“<all_urls>”]}
I searched for hours and hours and looked at several posts for similar problems but none of them have helped me. For example, this post suggests adding "<all_urls>" permission and it did not help in my case. Another post says that it is not possible to execute script in certain type of hosts such as about:[anything] pages and mozilla pages. I do not see my URL belongs to any of them.
Here is my example:
The manifest.json
{
"manifest_version": 2,
"name": "test",
"version": "1.0",
"background": {
"scripts": ["test.js"]
},
"permissions": [
"<all_urls>",
"activeTab",
"tabs",
"storage",
"webRequest"
]
}
The background script is test.js:
console.log("-- inside js file --");
var target = "<all_urls>";
function logError(responseDetails) {
errorTab=responseDetails.tabId;
console.log("response tab: "+errorTab);
var makeItGreen = 'document.body.style.border = "5px solid green"';
var executing = browser.tabs.executeScript(errorTab,{
code: makeItGreen
});
}//end function
browser.webRequest.onErrorOccurred.addListener(
logError,
{urls: [target],
types: ["main_frame"]}
);
The error you are seeing:
Error: No window matching {"matchesHost":["<all_urls>"]}
is generated when you attempt to inject a script using tabs.executeScript() (or CSS with tabs.insertCSS()) in a tab that is currently displaying a URL which you do not have permission to inject into. In this case, you have specified in your manifest.json the host permission "<all_urls>". The fact that "matchesHost":["<all_urls>"] is displayed indicates that Firefox is aware of your "<all_urls>" permission. That you have still gotten the error means that you have attempted to inject into a URL which does not match <all_urls>.
As you have mentioned, Firefox does not permit injecting into about:* pages. In addition, injecting into pages at the domain addons.mozilla.org is not permitted. None of those pages will match <all_urls>. All such URLs will generate the above error if you attempt to inject into tabs showing them.
But, I'm injecting into some normal URL that had an error
All easily obtainable information to the contrary — including the URL provided in the tabs.Tab data obtained from tabs.get() —, the page you are attempting to inject into is, in fact, an about:* page, not the page (that doesn't exist) at the URL where you got the error. While the URL reported in the tabs.tab structure for the tab in which you received the error will show the URL on which the error occurred, the actual URL for the page being displayed is something like:
about:neterror?e=dnsNotFound&u=[URL you were attempting to get to, but encoded as a query string]
I know this because the last webNavigation.onDOMContentLoaded event when I tested attempting to load the URL: http://www.exampleahdsafhd.com/ was:
webNavigation.onDOMContentLoaded - > arg[0] = Object {
url: "about:neterror?e=dnsNotFound&u=http%3A//www.exampleahdsafhd.com/&c=UTF-8&f=regular&d=Firefox%20can%E2%80%99t%20find%20the%20server%20at%20www.exampleahdsafhd.com.",
timeStamp: 1497389662844,
frameId: 0,
parentFrameId: -1,
tabId: 2,
windowId: 3
}
The fact that the error page is an about:* page, means that you will not be able to inject scripts, or CSS, into it. This means that you will need to find some other way to accomplish what you desire and/or adapt what you desire to do to what is possible. One possibility would be to navigate to a page within your extension which describes the error.
I have some Chrome extensions installed that need to be toggled on/off when using. The default state for all of them is off. However, when I access some particular webpages, I need to turn them all on (there is no option to filter pages in these extensions).
Since this is a chore, I am trying to create a Chrome extension that, on click, will simply toggle all of them on/off. Firstly, is it possible to do this? And, if yes, how do I go about doing this?
As an example, one of the extensions I want to automate is the TunnelBear VPN (Toggle on through my extension when required)
I'm using such an extension toggler myself, so I've copied some parts of it here.
Use chrome.management API to enable/disable the extensions.
Enable the extensions manually by a hotkey using chrome.commands API:
manifest.json, relevant parts:
"commands": {
"toggle": {
"suggested_key": {
"default": "Alt+T"
},
"description": "Toggle extensions"
}
},
"background": {
"scripts": ["background.js"],
"persistent": false
},
"permissions": ["commands", "management"]
background.js:
var IDs = ['aasdkfjhkjdfhdfjkhdkfjhdkjfh'];
chrome.commands.onCommand.addListener(function(command) {
IDs.forEach(function(ID) {
chrome.management.get(ID, function(oldState) {
chrome.management.setEnabled(ID, !oldState.enabled);
});
});
});
To get the IDs by short extension names:
var IDs;
chrome.management.getAll(function(info) {
IDs = info.filter(function(extension) {
return extension.shortName.match(/Name1|Name2|Name3/);
}).map(function(extension) {
return extension.id;
});
);
Alternatively, you can define two hotkeys to enable and disable the extension.
Enable the extensions automatically upon navigation to specified URLs
Some of the extensions may want to inject their content script on "document_start" so I guess we'll need chrome.webNavigation.onBeforeNavigate. Even though this event doesn't guarantee an actual navigation will occur, it's probably the only way to enable the extensions in time.
Use event filters to specify the URLs to activate, the possible criteria are listed in documentation.
// Enable the extension upon navigation to example.com
chrome.webNavigation.onBeforeNavigate.addListener(beforeNavigate, {
url: [{hostEquals: 'example.com'}, {urlContains: 'something'}]
});
function beforeNavigate(details) {
if (details.frameId === 0) {
setState(true);
}
}
function setState(newState) {
chrome.management.setEnabled('dhgfhdgfjgjhdgfjdfhdjhfdjhf', newState);
}
// And let's disable the extensions when that site is closed
chrome.tabs.onRemoved.addListener(function(tabId, info) {
chrome.tabs.get(tabId, function(tab) {
if (tab.url.indexOf('://example.com') > 0) {
setState(false);
}
});
});
// In the actual code you may want to track chrome.tabs.onUpdated too
// in order to detect in-tab navigation from example.com to another site
Required permissions: "webNavigation", "tabs"
Background page declaration can be the same as in #1.
You could try to use chrome.management API to disable/enable the extensions.
However, if the function you need from an extension depends on some trigger, such as a click on the extension, you can't replicate that.
I am in the process of building a Chrome extension, and for the whole thing to work the way I would like it to, I need an external JavaScript script to be able to detect if a user has my extension installed.
For example: A user installs my plugin, then goes to a website with my script on it. The website detects that my extension is installed and updates the page accordingly.
Is this possible?
Chrome now has the ability to send messages from the website to the extension.
So in the extension background.js (content.js will not work) add something like:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (request) {
if (request.message) {
if (request.message == "version") {
sendResponse({version: 1.0});
}
}
}
return true;
});
This will then let you make a call from the website:
var hasExtension = false;
chrome.runtime.sendMessage(extensionId, { message: "version" },
function (reply) {
if (reply) {
if (reply.version) {
if (reply.version >= requiredVersion) {
hasExtension = true;
}
}
}
else {
hasExtension = false;
}
});
You can then check the hasExtension variable. The only drawback is the call is asynchronous, so you have to work around that somehow.
Edit:
As mentioned below, you'll need to add an entry to the manifest.json listing the domains that can message your addon. Eg:
"externally_connectable": {
"matches": ["*://localhost/*", "*://your.domain.com/*"]
},
2021 Update:
chrome.runtime.sendMessage will throw the following exception in console if the extension isn't installed or it's disabled.
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist
To fix this, add this validation inside the sendMessage callback
if (chrome.runtime.lastError) {
// handle error
}
I am sure there is a direct way (calling functions on your extension directly, or by using the JS classes for extensions), but an indirect method (until something better comes along):
Have your Chrome extension look for a specific DIV or other element on your page, with a very specific ID.
For example:
<div id="ExtensionCheck_JamesEggersAwesomeExtension"></div>
Do a getElementById and set the innerHTML to the version number of your extension or something. You can then read the contents of that client-side.
Again though, you should use a direct method if there is one available.
EDIT: Direct method found!!
Use the connection methods found here: https://developer.chrome.com/extensions/extension#global-events
Untested, but you should be able to do...
var myPort=chrome.extension.connect('yourextensionid_qwerqweroijwefoijwef', some_object_to_send_on_connect);
Another method is to expose a web-accessible resource, though this will allow any website to test if your extension is installed.
Suppose your extension's ID is aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, and you add a file (say, a transparent pixel image) as test.png in your extension's files.
Then, you expose this file to the web pages with web_accessible_resources manifest key:
"web_accessible_resources": [
"test.png"
],
In your web page, you can try to load this file by its full URL (in an <img> tag, via XHR, or in any other way):
chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/test.png
If the file loads, then the extension is installed. If there's an error while loading this file, then the extension is not installed.
// Code from https://groups.google.com/a/chromium.org/d/msg/chromium-extensions/8ArcsWMBaM4/2GKwVOZm1qMJ
function detectExtension(extensionId, callback) {
var img;
img = new Image();
img.src = "chrome-extension://" + extensionId + "/test.png";
img.onload = function() {
callback(true);
};
img.onerror = function() {
callback(false);
};
}
Of note: if there is an error while loading this file, said network stack error will appear in the console with no possibility to silence it. When Chromecast used this method, it caused quite a bit of controversy because of this; with the eventual very ugly solution of simply blacklisting very specific errors from Dev Tools altogether by the Chrome team.
Important note: this method will not work in Firefox WebExtensions. Web-accessible resources inherently expose the extension to fingerprinting, since the URL is predictable by knowing the ID. Firefox decided to close that hole by assigning an instance-specific random URL to web accessible resources:
The files will then be available using a URL like:
moz-extension://<random-UUID>/<path/to/resource>
This UUID is randomly generated for every browser instance and is not your extension's ID. This prevents websites from fingerprinting the extensions a user has installed.
However, while the extension can use runtime.getURL() to obtain this address, you can't hard-code it in your website.
I thought I would share my research on this.
I needed to be able to detect if a specific extension was installed for some file:/// links to work.
I came across this article here
This explained a method of getting the manifest.json of an extension.
I adjusted the code a bit and came up with:
function Ext_Detect_NotInstalled(ExtName, ExtID) {
console.log(ExtName + ' Not Installed');
if (divAnnounce.innerHTML != '')
divAnnounce.innerHTML = divAnnounce.innerHTML + "<BR>"
divAnnounce.innerHTML = divAnnounce.innerHTML + 'Page needs ' + ExtName + ' Extension -- to intall the LocalLinks extension click here';
}
function Ext_Detect_Installed(ExtName, ExtID) {
console.log(ExtName + ' Installed');
}
var Ext_Detect = function (ExtName, ExtID) {
var s = document.createElement('script');
s.onload = function () { Ext_Detect_Installed(ExtName, ExtID); };
s.onerror = function () { Ext_Detect_NotInstalled(ExtName, ExtID); };
s.src = 'chrome-extension://' + ExtID + '/manifest.json';
document.body.appendChild(s);
}
var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
if (is_chrome == true) {
window.onload = function () { Ext_Detect('LocalLinks', 'jllpkdkcdjndhggodimiphkghogcpida'); };
}
With this you should be able to use Ext_Detect(ExtensionName,ExtensionID) to detect the installation of any number of extensions.
Another possible solution if you own the website is to use inline installation.
if (chrome.app.isInstalled) {
// extension is installed.
}
I know this an old question but this way was introduced in Chrome 15 and so I thought Id list it for anyone only now looking for an answer.
Here is an other modern approach:
const checkExtension = (id, src, callback) => {
let e = new Image()
e.src = 'chrome-extension://'+ id +'/'+ src
e.onload = () => callback(1), e.onerror = () => callback(0)
}
// "src" must be included to "web_accessible_resources" in manifest.json
checkExtension('gighmmpiobklfepjocnamgkkbiglidom', 'icons/icon24.png', (ok) => {
console.log('AdBlock: %s', ok ? 'installed' : 'not installed')
})
checkExtension('bhlhnicpbhignbdhedgjhgdocnmhomnp', 'images/checkmark-icon.png', (ok) => {
console.log('ColorZilla: %s', ok ? 'installed' : 'not installed')
})
I used the cookie method:
In my manifest.js file I included a content script that only runs on my site:
"content_scripts": [
{
"matches": [
"*://*.mysite.co/*"
],
"js": ["js/mysite.js"],
"run_at": "document_idle"
}
],
in my js/mysite.js I have one line:
document.cookie = "extension_downloaded=True";
and in my index.html page I look for that cookie.
if (document.cookie.indexOf('extension_downloaded') != -1){
document.getElementById('install-btn').style.display = 'none';
}
You could have the extension set a cookie and have your websites JavaScript check if that cookie is present and update accordingly. This and probably most other methods mentioned here could of course be cirvumvented by the user, unless you try and have the extension create custom cookies depending on timestamps etc, and have your application analyze them server side to see if it really is a user with the extension or someone pretending to have it by modifying his cookies.
There's another method shown at this Google Groups post. In short, you could try detecting whether the extension icon loads successfully. This may be helpful if the extension you're checking for isn't your own.
Webpage interacts with extension through background script.
manifest.json:
"background": {
"scripts": ["background.js"],
"persistent": true
},
"externally_connectable": {
"matches": ["*://(domain.ext)/*"]
},
background.js:
chrome.runtime.onMessageExternal.addListener(function(msg, sender, sendResponse) {
if ((msg.action == "id") && (msg.value == id))
{
sendResponse({id : id});
}
});
page.html:
<script>
var id = "some_ext_id";
chrome.runtime.sendMessage(id, {action: "id", value : id}, function(response) {
if(response && (response.id == id)) //extension installed
{
console.log(response);
}
else //extension not installed
{
console.log("Please consider installig extension");
}
});
</script>
Your extension could interact with the website (e.g. changing variables) and your website could detect this.
But there should be a better way to do this. I wonder how Google is doing it on their extension gallery (already installed applications are marked).
Edit:
The gallery use the chrome.management.get function. Example:
chrome.management.get("mblbciejcodpealifnhfjbdlkedplodp", function(a){console.log(a);});
But you can only access the method from pages with the right permissions.
A lot of the answers here so far are Chrome only or incur an HTTP overhead penalty. The solution that we are using is a little different:
1. Add a new object to the manifest content_scripts list like so:
{
"matches": ["https://www.yoursite.com/*"],
"js": [
"install_notifier.js"
],
"run_at": "document_idle"
}
This will allow the code in install_notifier.js to run on that site (if you didn't already have permissions there).
2. Send a message to every site in the manifest key above.
Add something like this to install_notifier.js (note that this is using a closure to keep the variables from being global, but that's not strictly necessary):
// Dispatch a message to every URL that's in the manifest to say that the extension is
// installed. This allows webpages to take action based on the presence of the
// extension and its version. This is only allowed for a small whitelist of
// domains defined in the manifest.
(function () {
let currentVersion = chrome.runtime.getManifest().version;
window.postMessage({
sender: "my-extension",
message_name: "version",
message: currentVersion
}, "*");
})();
Your message could say anything, but it's useful to send the version so you know what you're dealing with. Then...
3. On your website, listen for that message.
Add this to your website somewhere:
window.addEventListener("message", function (event) {
if (event.source == window &&
event.data.sender &&
event.data.sender === "my-extension" &&
event.data.message_name &&
event.data.message_name === "version") {
console.log("Got the message");
}
});
This works in Firefox and Chrome, and doesn't incur HTTP overhead or manipulate the page.
You could also use a cross-browser method what I have used.
Uses the concept of adding a div.
in your content script (whenever the script loads, it should do this)
if ((window.location.href).includes('*myurl/urlregex*')) {
$('html').addClass('ifextension');
}
in your website you assert something like,
if (!($('html').hasClass('ifextension')){}
And throw appropriate message.
If you have control over the Chrome extension, you can try what I did:
// Inside Chrome extension
var div = document.createElement('div');
div.setAttribute('id', 'myapp-extension-installed-div');
document.getElementsByTagName('body')[0].appendChild(div);
And then:
// On web page that needs to detect extension
if ($('#myapp-extension-installed-div').length) {
}
It feels a little hacky, but I couldn't get the other methods to work, and I worry about Chrome changing its API here. It's doubtful this method will stop working any time soon.
If you're trying to detect any extension from any website,
This post helped: https://ide.hey.network/post/5c3b6c7aa7af38479accc0c7
Basically, the solution would be to simply try to get a specific file (manifest.json or an image) from the extension by specifying its path. Here's what I used. Definitely working:
const imgExists = function(_f, _cb) {
const __i = new Image();
__i.onload = function() {
if (typeof _cb === 'function') {
_cb(true);
}
}
__i.onerror = function() {
if (typeof _cb === 'function') {
_cb(false);
}
}
__i.src = _f;
__i = null;
});
try {
imgExists("chrome-extension://${CHROME_XT_ID}/xt_content/assets/logo.png", function(_test) {
console.log(_test ? 'chrome extension installed !' : 'chrome extension not installed..');
ifrm.xt_chrome = _test;
// use that information
});
} catch (e) {
console.log('ERROR', e)
}
Here is how you can detect a specific Extension installed and show a warning message.
First you need to open the manifest file of the extension by going to chrome-extension://extension_id_here_hkdppipefbchgpohn/manifest.json and look for any file name within "web_accessible_resources" section.
<div class="chromewarning" style="display:none">
<script type="text/javascript">
$.get("chrome-extension://extension_id_here_hkdppipefbchgpohn/filename_found_in_ web_accessible_resources.png").done(function () {
$(".chromewarning").show();
}).fail(function () {
// alert("failed.");
});
</script>
<p>We have detected a browser extension that conflicts with learning modules in this course.</p>
</div>
Chrome Extension Manifest v3:
const isFirefox = chrome.runtime.OnInstalledReason.CHROME_UPDATE != "chrome_update";
For FireFox, I believe chrome.runtime.OnInstalledReason.BROWSER_UPDATE will be "browser_update": https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/OnInstalledReason
I developed a google chrome extension that works good with Windows OS. but the problem that does not work with mac, I tried to check the problem, I found that function
chrome.tabs.executeScript
does not work on mac, this is the completely code that worked good with windows not mac.
// The onClicked callback function.
function onClickHandler(info, tab) {
// the problem in injecting code
chrome.tabs.executeScript
( null,{code:"var activeElm = document.activeElement; var inp_text = activeElm.value; console.log(activeElm.value);"});
};
chrome.contextMenus.onClicked.addListener(onClickHandler);
// Set up context menu tree at install time.
chrome.runtime.onInstalled.addListener(function() {
// Intentionally create an invalid item, to show off error checking in the
// create callback.
console.log("About to try creating an invalid item - an error about " +
"duplicate item child1 should show up");
chrome.contextMenus.create({"title": "consoleMe", "id": "child523", "contexts":["selection"]}, function() {
if (chrome.extension.lastError) {
console.log("Got expected error: " + chrome.extension.lastError.message);
}
});
});
Hint:
I use content_scripts injection files in the manifest file, but it seems did not activated on Google chrome on Mac.
"content_scripts": [
{
"matches": ["http://*/*","https://*/*"],
"js" : ["jquery.min.js","fix.js","injscript.js"],
"all_frames": false
}
],
OS : Mac 10.8 Mountain lion --
Google Chrome V 32
There should be no difference between Windows and Mac. It's likely that something else is different between your two setups.
It's not clear to me whether the javascript snippet above is a background script or an injected content script. Can you provide the full manifest and indicate which file the snippet belongs to?
If it's a background script, chrome.tabs.executeScript with null tabId will apply to the background page, which is not what you expect. Instead, pass tab.id for the tabId.
If it's a content script, the contextMenus API calls won't work because content scripts are not allowed to use chrome APIs (see https://developer.chrome.com/extensions/content_scripts.html).