support
support copied to clipboard
[FireMonkey] Please add Firemonkey to `window.external`
according to this, userscript pages rely on the window.external.xxxx
to check whether the script is installed or not.
This is already well implemented in Tampermonkey and Violentmonkey, FireMonkey shall follow this practice and ask those pages like "Greasy Fork" to update accordingly.
Without window.external.xxxx
, no one can detect the script is installed or not in FireMonkey.
Suggested Usage
window.external.FireMonkey (or Firemonkey)
- string property
version
- indicate the installed version of FireMonkey. - function property
isInstalled
- accepts two string parameters (name
andnamespace
) and return aPromise
.- return the version code if it is installed; return null if it is not installed.
according to this, userscript pages rely on the window.external.xxxx to check whether the script is installed or not.
Window: external property is deprecated. Furthermore, window
object can be accessible to the webpage which then can interfere with the userscript.
If you mean that the userscript can check which manager it is running on, then the proper method would be GM_info.scriptHandler
and GM.info.scriptHandler
.
Manager version is also available under GM.info.version
.
Script name and other details are available under GM.info.script
.
So how can we know other scripts are installed or not?
For example, I can run in the devTools to check whether a script "MyUserscript" is installed or not.
window.external.Violentmonkey.isInstalled('MyUserscript', 'namespace.com').then(result=>{
if(result) console.log(`MyUserscript is installed, version = ${result}`);
else console.log(`MyUserscript is not installed.`);
});
Even I create a UserScript installed in Firemonkey, I can just have GM.info refer to the current script. It is still unable to obtain the installation status of a particular userscript.
So how can we know other scripts are installed or not?
Userscript managers show the active userscripts in their UI. Each userscript manager should have details of its own installation, not another extension's installation.
window
in GM|TM|VM in content
context is not the same window
as userScript
context used by FireMonkey. Even if such a variable is added in FM, it wont be accessible to GM|TM|VM.
content
context window
object is accessible to the webpage.
Webpages should not be allowed to check if user has installed other extensions or userscripts.
Furthermore, a userscript shall not be allowed to check if user has installed other extensions or userscripts.
I am still unsure of what are you trying to achieve.
That means it is never possible to let GreasyFork to detect the script is installed or not? Even through there is a userscript written by someone to do this intentionally ?
That means it is never possible to let GreasyFork to detect the script is installed or not?
ATM, webpages won't be able to check if a userscript is installed in FireMonkey. Why should it be allowed?
Preamble
Please note the following:
- Window: external property is deprecated
- There can be privacy/security concerns
- Support is not unified
- It appears to be an arrangement between GreasyFork (and a few others) and TM|VM|Stylus
GreasyFork
GreasyFork only checks TM|VM & Stylus
https://github.com/JasonBarnabe/greasyfork/blob/e198546a9c8e61556aee756932abf222506f4541/app/javascript/managers.js#L1-L21
export function getTampermonkey() {
return window.external?.Tampermonkey
}
export function getViolentmonkey() {
return window.external?.Violentmonkey
}
export function canInstallUserJS() {
return getTampermonkey() || getViolentmonkey()
}
export async function canInstallUserCSS() {
if (localStorage.getItem('stylusDetected') == '1') {
return true;
}
postMessage({ type: 'style-version-query', name: "whatever", namespace: "whatever", url: location.href }, location.origin);
return new Promise(resolve => setTimeout(function() {
resolve(localStorage.getItem('stylusDetected') == '1')
}, 200));
}
https://github.com/JasonBarnabe/greasyfork/blob/f7dae27d036f02753c17644bb26a0f1b8366bf22/app/javascript/versioncheck.js#L3-L25
function getInstalledVersion(name, namespace) {
return new Promise(function(resolve, reject) {
let tm = getTampermonkey()
if (tm) {
tm.isInstalled(name, namespace, function (i) {
if (i.installed) {
resolve(i.version);
} else {
resolve(null);
}
});
return;
}
let vm = getViolentmonkey();
if (vm) {
vm.isInstalled(name, namespace).then(resolve);
return;
};
reject()
});
}
Greasemonkey
-
window.external
is not supported
Tampermonkey
- Added in v4.2 (2016-11-24) as experimental
- Available on tampermonkey.net, greasyfork.org, sleazyfork.org, openuserjs.org, userstyles.org, userscript.zone via optional settings
- Available on normal tabs, but not in containers or private window
-
namespace
is optional - Methods returns a callback
- Return value is an
object
- See also: Add an option to allow communication with cooperate pages
window.external.Tampermonkey = {
getVersion() { },
openOptions() { },
isInstalled() { }
}
Examples
!!window.external?.Tampermonkey
// true
window.external.Tampermonkey.getVersion(console.log)
// Object { version: "4.19.0", id: "fire" }
window.external.Tampermonkey.isInstalled('W.A.R. Links Checker Premium', 'premium version', console.log)
window.external.Tampermonkey.isInstalled('W.A.R. Links Checker Premium', console.log)
// Object { name: "W.A.R. Links Checker Premium", installed: true, enabled: true, version: "1.5.7.8" }
window.external.Tampermonkey.isInstalled('xyz', console.log)
// Object { name: "xyz", installed: false, enabled: undefined, version: undefined }
window.external.Tampermonkey.openOptions()
// open options page, allow web site to navigate user to the TM options page
// background.js
else if("openOptions"==e.method){let r=e.params||"";r&&!r.startsWith("#")&&(r="#"+r),q.tabs.update(t.tab.id,{url:q.extension.getURL("options.html")+r,active:!0},()=>n({}))
window.external.Tampermonkey.openOptions('hash')
// open options page and navigate to the hash anchor, allow web site to navigate user to the TM options page
Violentmonkey
- Added in v2.12.8 (2020-12-15)
- Available on greasyfork.org (default true), and sleazyfork.org (default false) via optional settings
- Available on normal tabs, containers and private window
-
namespace
is mandatory - Method returns a Promise
- Return value is a
string
ornull
- See also: feat: make exposing via window.external optional
window.external.Violentmonkey = {
version: '2.15.0',
isInstalled() { }
}
Examples
!!window.external?.Violentmonkey
// true
window.external.Violentmonkey.version
// "2.15.0"
window.external.Violentmonkey.isInstalled('W.A.R. Links Checker Premium', 'premium version').then(console.log)
// "1.6.2.2"
window.external.Violentmonkey.isInstalled('W.A.R. Links Checker Premium').then(console.log)
window.external.Violentmonkey.isInstalled('xyz', 'premium').then(console.log)
// null
Privacy/Security
Wherever the object is available:
- The website can snoop on which userscript managers and scripts, user has installed
- Other userscripts can snoop on which userscript managers and scripts, user has installed e.g. Greasyfork Search with Sleazyfork Results include, Greasy Fork+, VM detector, VM detector, TagPro ModFather
🔶 🔷 🔶 🔷 🔶 🔷 🔶 🔷 🔶 🔷 🔶
window.external from the UserScript
However, a userscript can announce itself by creating the object. Here is an example:
Synchronous Example
// ==UserScript==
// @name window.external #582
// @description Please add FireMonkey to window.external #582
// @match https://greasyfork.org/*
// @match https://sleazyfork.org/*
// @version 1.0
// ==/UserScript==
const js = `window.external.FireMonkey = {
version: ${JSON.stringify(GM.info.version)},
isInstalled(name, namespace) {
// only for self, return script version
return name === ${JSON.stringify(GM.info.script.name)} ? ${JSON.stringify(GM.info.script.version)} : null;
}
};`;
GM.addElement('script', {textContent: js});
window.external.FireMonkey.version
// "2.73"
window.external.FireMonkey.isInstalled('window.external #582')
// "1.0"
Asynchronous Example
// ==UserScript==
// @name window.external #582
// @description Please add FireMonkey to window.external #582
// @match https://greasyfork.org/*
// @match https://sleazyfork.org/*
// @version 1.0
// ==/UserScript==
const js = `window.external.FireMonkey = {
version: ${JSON.stringify(GM.info.version)},
async isInstalled(name, namespace) {
// only for self, return script version
return name === ${JSON.stringify(GM.info.script.name)} ? ${JSON.stringify(GM.info.script.version)} : null;
}
};`;
GM.addElement('script', {textContent: js});
window.external.FireMonkey.isInstalled('window.external #582').then(console.log)
// 1.0
Honestly this feature looks dangerous, and it's not that useful rather not relying on it and have a better coding practice.