How do I detect Chromium specifically vs. Chrome? - google-chrome

Is there a way to detect if a visitor to my site is running Chromium as opposed to Google Chrome? Even basic UA sniffing (which I know is bad practice) would suffice for my particular case, but it appears that Chromium and Chrome share the same UA string – is that correct? Is there any other way that I can differentiate between the two?

Note:
This no longer works, because now all Chrome-based-navigators have all plugins.
New Chromium-versions do have the PDF-plugin, too.
But they also have Chromium-plugins, so if any plugin starts with "Chromium", it's Chromium:
function isChromium() {
for (var i = 0, u = "Chromium", l = u.length; i < navigator.plugins.length; i++) {
if (navigator.plugins[i].name != null && navigator.plugins[i].name.substr(0, l) === u)
return true;
}
return false;
}
Also, use this to identify Microsoft Chredge (aka. Anaheim)
function isEdg() {
for (var i = 0, u = "Microsoft Edg", l = u.length; i < navigator.plugins.length; i++) {
if (navigator.plugins[i].name != null && navigator.plugins[i].name.substr(0, l) === u)
return true;
}
return false;
}

Chrome ships with a built-in PDF reader, Chromium doesn't.
You could detect this by using JavaScript:
function isChrome() { // Actually, isWithChromePDFReader
for (var i=0; i<navigator.plugins.length; i++)
if (navigator.plugins[i].name == 'Chrome PDF Viewer') return true;
return false;
}
This method is not 100% reliable, because users can copy the PDF reader binary from Chrome to their Chromium directory, see this answer on Ask Ubuntu.
There's almost no difference between Chromium and Chrome (certainly not in the rendering or JavaScript engine), so why do you want to spot the difference?

Starting with Chromium 84 there's a new method called User-Agent Client Hints reference
You can check if the userAgentData property exists and look for brand data. It will return an array that looks something like this.
[{
"brand": " Not;A Brand",
"version": "99"
}, {
"brand": "Google Chrome",
"version": "91"
}, {
"brand": "Chromium",
"version": "91"
}]
userAgentData.brands will contain varying values in a varying order, so don't rely on something appearing at a certain index. Instead check if the property exists in the array.
if (navigator.userAgentData) {
let vendors = window.navigator.userAgentData.brands;
if (vendors.filter(e => e.brand === 'Google Chrome').length > 0) {
console.log('Chrome')
} else {
console.log('Chromium')
}
}

Here is a variation to Paul W.'s answer that works for Chromium version 42 and above:
function isChromium() { // Actually, isWithChromiumPDFReader
for (var i=0; i<navigator.plugins.length; i++)
if (navigator.plugins[i].name == 'Chromium PDF Viewer') return true;
return false;
}
This of course only works if the plugin has not been disabled by the user.

Here is another way, using SpeechSynthesis feature.
Google Chrome Browser ships TTS voices, where Chromium browsers (incl. Brave) do not. Voices can be installed manually, with espeak (on linux) however the Google voices all start with Google, where the manually installed voices do not. As far as I know the Chrome voices are propriety, not free.
The collection of voices is an Array where each voices looks like this:
{
voiceURI: "Google Deutsch",
name: "Google Deutsch",
lang: "de-DE",
localService: false,
default: true
}
We just need to find one who's name/URI starts with Google ...
function hasGoogleVoices() {
return window.speechSynthesis.getVoices()
.some(v => /^google/i.test(v.name));
}
(Tested on Linux for Chrome, Brave, Chromium and Firefox)
Please can someone check Safari and Windows. Thx.

Could not comment on https://stackoverflow.com/a/68428992/14238203 Josh Answer.
On latest Chrome and Chromium (Oct 2021) some of the solutions returns true for both, so I had to find a different solution.
I took https://stackoverflow.com/a/63724166/14238203 fliptopbox code and implmented Josh answer.
const isChrome = navigator.userAgentData.brands.some((v) => /^google/i.test(v.brand));
The issue with Josh answer is that if you try this when just loading a page, the getVoices() returns empty array until all the voices are loaded (page finished loading)
A promise solution to that here - https://stackoverflow.com/a/59786665/14238203
For my use case it was a bit cumbersome with the getVoices() so I used the user agent hints solution.

Related

Can a website detect extensions that are installed using Developed Mode? [duplicate]

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

Detect if another browser tab is using speechRecognition

Is it possible to tell if another Chrome tab is using webkitSpeechRecognition?
If you try to use webkitSpeechRecognition while another tab is using it, it will throw an error "aborted" without any message. I want to be able to know if webkitSpeechRecognition is open in another tab, and if so, throw a better error that could notify the user.
Unless your customer is on the same website(you could check by logging the ip/browserprint in database and requesting by json) you cannot do that.
Cross domain protection is in effect, and that lets you know zilch about what happens in other tabs or frames.
I am using webkitSpeechRecognition for chrome ( does not work on FF) and I faced same issues like multiple Chrome tabs. Until the browser implement a better error message a temporary solutions that work for me:
You need to detect when a tab is focused or not in Chrome using
Javascript.
Make javascript code like this
isChromium = window.chrome;
if(isChromium)
{
if (window.addEventListener)
{
// bind focus event
window.addEventListener("focus", function (event)
{
console.log("Browser tab focus..");
recognition.stop();// to avoid error
recognition.start();
}, false);
window.addEventListener("blur", function (event)
{
console.log("Browser tab blur..");
recognition.stop();
}, false);
}
}
There's a small workaround for it. You can store the timestamp in a variable upon activating SpeechRecognition and when it exits after a few seconds of inactivity, it will be compared to a timestamp since the SpeechRecognition was activated. Since two tabs are using the API simultaneously, it will exit immediately.
For Chrome, you can use the code below and modify it base on your needs. Firefox doesn't support this yet at the moment.
var transcriptionStartTime;
var timeSinceLastStart;
function configureRecognition(){
var webkitSpeechRecognition = window.webkitSpeechRecognition || window.SpeechRecognition;
if ('webkitSpeechRecognition' in window) {
recognition = new webkitSpeechRecognition();
recognition.continuous = true;
recognition.interimResults = true;
recognition.lang = "en-US";
recognition.onend = function() {
timeSinceLastStart = new Date().getTime() - transcriptionStartTime;
if (timeSinceLastStart < 100) {
alert('Speech recognition failed to start. Please close the tab that is currently using it.');
}
}
}
}
See browser compatibility here: https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition

Can I disable Firefox / Chrome components from an addon / extension?

I already made some addons for Firefox and extensions for Chrome, but now I had a crazy idea and I would like to know if I can disable Firefox / Chrome components from an addon / extension.
When I say disable components, I mean something like (mostly FF examples):
Firefox Hello
Pocket (Firefox has now a default integration with Pocket)
History
Favorites
Other installed extensions
Resources like "Print" and "Developer Tools"
Etc.
I've searched for the whole Firefox Addon Developer Hub and I didn't found if I can do something like that. If you know the answer, how can I do that or why I can't?
You don't need to describe why it's (or isn't) possible and how I can achieve that, but in this case provide useful and interesting links.
From Firefox it is very easy to disable other things.
This for example disables an addon by id:
//this checks to see if AdBlock Plus is enabled
AddonManager.getAddonsByIDs(["{d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d}"], function ([aAddon1]) {
console.log(aAddon1);
var isAddonEnabled = aAddon1.isActive;
alert('AdBlock plus enabled = ' + isAddonEnabled)
//for other properties see here: https://developer.mozilla.org/en-US/Add-ons/Add-on_Manager/Addon#Required_properties
});
Components are done a little differently, you would have to use nsICategoryManager, this disables the default pdf reader:
https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsICategoryManager#Remarks
var CONTENT_TYPE = 'application/pdf';
// Update the category manager in case the plugins are already loaded.
let categoryManager = Cc['#mozilla.org/categorymanager;1'];
categoryManager.getService(Ci.nsICategoryManager).deleteCategoryEntry('Gecko-Content-Viewers', CONTENT_TYPE, false);
// Update pref manager to prevent plugins from loading in future
var stringTypes = '';
var types = [];
var PREF_DISABLED_PLUGIN_TYPES = 'plugin.disable_full_page_plugin_for_types';
if (Services.prefs.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES)) {
stringTypes = Services.prefs.getCharPref(PREF_DISABLED_PLUGIN_TYPES);
}
if (stringTypes !== '') {
types = stringTypes.split(',');
}
if (types.indexOf(CONTENT_TYPE) === -1) {
types.push(CONTENT_TYPE);
}
Services.prefs.setCharPref(PREF_DISABLED_PLUGIN_TYPES, types.join(','));

HTML5 Video Tag Volume Support

i had a question
on some devices like iPad and Android Tables you cannt change Volume of Video Tags becuase Volume API dont supported on this devices. have yo a idea how i can detect if this isnt support?
The best I could come up with is this:
function volumeChangeSupported () {
var ua = navigator.userAgent.toLowerCase();
// got information from jplayer:
var noVolume = /ipad|iphone|ipod|android|blackberry|windows ce|windows phone|webos|playbook/.exec(ua);
if (noVolume) {
if (noVolume[0] === 'android' && /gecko/.test(ua)) {
// Firefox on android DOES support changing the volume:
return true;
}
else {
return false;
}
}
return true;
}
This doesn't really "detect" support for changing the volume. I got this information partly from jPlayer and partly from my own experience testing Firefox 19 on an old Android 3 tablet. Who knows if Firefox on an Android phone or a different Android version behaves differently.
But before this I tried to detect volume change support like this:
function volumeChangeSupported () {
var audio = new Audio();
audio.volume = 0.5;
return audio.volume === 0.5;
}
This yielded the correct result for iPhone Safari and Android Firefox, but not for other Android Browsers (the "Android Browser" and "Dolphin", which can't change the volume but had audio.volume === 0.5 to be true).

IndexedDB not working in FireFox and IE

I have the latest versions of Firefox and IE, but the example in html5rocks.com is not working in these two browsers. I tested with Chrome and it works fine.
I have noted that these browsers does not fire any event ('onsuccess' or 'onerror') on attempting to open the indexedDB as follows.
var request = indexedDB.open("todos");
Please share any ideas/solution for this issue.
to make html5rocks demo work on Firefox you need to attach onupgradeneeded event on database open instead of setversion method for creating the database.
here is the code sample that works both on Firefox and Chrome:
myStorage.indexedDB.open = function() {
var v = 1;
var request = indexedDB.open("todos", v);
//Firefox code for db init
request.onupgradeneeded = function (e) {
myStorage.indexedDB.db = e.target.result;
var db = myStorage.indexedDB.db;
// We can only create Object stores in a setVersion transaction;
if(db.objectStoreNames.contains("todo")) {
var storeReq = db.deleteObjectStore("todo");
}
var store = db.createObjectStore("todo",
{keyPath: "timeStamp"});
}
request.onsuccess = function(e) {
myStorage.indexedDB.db = e.target.result;
var db = myStorage.indexedDB.db;
//Chrome code for db init
if (v!= db.version && db.setVersion) {
var setVrequest = db.setVersion(v);
// onsuccess is the only place we can create Object Stores
setVrequest.onerror = myStorage.indexedDB.onerror;
setVrequest.onsuccess = function(e) {
if(db.objectStoreNames.contains("todo")) {
db.deleteObjectStore("todo");
}
var store = db.createObjectStore("todo",
{keyPath: "timeStamp"});
myStorage.indexedDB.getAllTodoItems();
};
}
else
myStorage.indexedDB.getAllTodoItems();
};
request.onerror = myStorage.indexedDB.onerror;
}
Edit: Here is a link to working version of the html5roks ToDo demo, which I maintain on github, expanded with two new features for viewing details data and updating values.
Chrome is behind on the IndexedDB standard.
There was a new revision introduced in December and Firefox and IE have upgraded. Chrome has not yet.
I believe it's mostly folks from the Chrome crew who run HTML5Rocks.com so it makes sense why these examples would be behind.
The big change between the pre and post-December 2010 API are the change in setVersion requests and the new onupgradeneeded callback.
Todo list example is outdated IndexedDB specification implemented on chrome. Now you got to use onupgardedneeded method, and well setVersion too for now.
IndexedDB is not supported on IE. Visit html5test.com for verification
Edit 2015: IndexedDB API is available in IE as of version 10