fix USO install button

This commit is contained in:
tophf 2022-09-16 12:02:04 +03:00
parent efc6d09d49
commit d1f5468a81

View File

@ -10,13 +10,12 @@
const USO = 'https://userstyles.org'; const USO = 'https://userstyles.org';
const apiUrl = `${USO}/api/v1/styles/${usoId}`; const apiUrl = `${USO}/api/v1/styles/${usoId}`;
const md5Url = `https://update.userstyles.org/${usoId}.md5`; const md5Url = `https://update.userstyles.org/${usoId}.md5`;
const CLICK = { const CLICK = [
customize: '.customize_button', ['#install_stylish_style_button', onInstall],
install: '#install_style_button', ['#update_stylish_style_button', onInstall],
uninstall: '#uninstall_style_button', ['.customize_style_button', onCustomize],
update: '#update_style_button', ['.uninstall_stylish_style_button', onUninstall],
}; ];
const CLICK_SEL = Object.values(CLICK).join(',');
const pageEventId = `${performance.now()}${Math.random()}`; const pageEventId = `${performance.now()}${Math.random()}`;
const contentEventId = pageEventId + ':'; const contentEventId = pageEventId + ':';
const orphanEventId = chrome.runtime.id; // id won't be available in the orphaned script const orphanEventId = chrome.runtime.id; // id won't be available in the orphaned script
@ -43,7 +42,7 @@
document.body || new Promise(resolve => addEventListener('load', resolve, {once: true})), document.body || new Promise(resolve => addEventListener('load', resolve, {once: true})),
]); ]);
if (!dup.id) { if (!dup) {
sendStylishEvent('styleCanBeInstalledChrome'); sendStylishEvent('styleCanBeInstalledChrome');
} else if (dup.originalMd5 && dup.originalMd5 !== md5 || !dup.usercssData || !dup.md5Url) { } else if (dup.originalMd5 && dup.originalMd5 !== md5 || !dup.usercssData || !dup.md5Url) {
// allow update if 1) changed, 2) is a classic USO style, 3) is from USO-archive // allow update if 1) changed, 2) is a classic USO style, 3) is from USO-archive
@ -53,23 +52,29 @@
} }
async function onClick(e) { async function onClick(e) {
const el = e.target.closest(CLICK_SEL); for (const [sel, fn] of CLICK) {
if (!el) return; const el = e.target.closest(sel);
el.disabled = true; if (!el) continue;
const {id} = dup;
try { try {
if (el.matches(CLICK.uninstall)) { el.disabled = true;
dup = style = false; await fn(e);
removeEventListener('change', onChange); } catch (e) {
await API.styles.delete(id); alert(chrome.i18n.getMessage('styleInstallFailed', e.message || e));
return; } finally {
el.disabled = false;
} }
if (el.matches(CLICK.customize)) {
const isOn = dup && !$('#style-settings');
toggleListener(isOn, 'change', onChange);
observeColors(isOn);
return;
} }
}
function onCustomize() {
const ss = $('#style-settings');
const willShow = !ss || !ss.offsetHeight;
observeColors(willShow);
toggleListener(willShow, 'change', onChange);
}
async function onInstall(e) {
const {id} = dup;
e.stopPropagation(); e.stopPropagation();
if (!style) await buildStyle(); if (!style) await buildStyle();
style = dup = await API.usercss.install(style, { style = dup = await API.usercss.install(style, {
@ -78,11 +83,14 @@
}); });
sendStylishEvent('styleInstalledChrome'); sendStylishEvent('styleInstalledChrome');
API.uso.pingback(id); API.uso.pingback(id);
} catch (e) {
alert(chrome.i18n.getMessage('styleInstallFailed', e.message || e));
} finally {
el.disabled = false;
} }
function onUninstall() {
const {id} = dup;
dup = style = false;
observeColors(false);
removeEventListener('change', onChange);
return API.styles.delete(id);
} }
function onChange({target: el}) { function onChange({target: el}) {
@ -121,7 +129,7 @@
const {vars} = (style || dup).usercssData; const {vars} = (style || dup).usercssData;
for (const el of document.querySelectorAll('[name^="ik-"]')) { for (const el of document.querySelectorAll('[name^="ik-"]')) {
const name = el.name.slice(3); // dropping "ik-" const name = el.name.slice(3); // dropping "ik-"
const ik = badKeys[name] || name; const ik = (badKeys || {})[name] || name;
const v = vars[ik] || false; const v = vars[ik] || false;
const isImage = el.type === 'radio'; const isImage = el.type === 'radio';
if (v && (!isImage || el.checked)) { if (v && (!isImage || el.checked)) {
@ -185,45 +193,56 @@
})(); })();
function inPageContext(eventId, eventIdHost, styleId, apiUrl) { function inPageContext(eventId, eventIdHost, styleId, apiUrl) {
let done, orphaned, vars;
if (!window.chrome) window.chrome = {runtime: {sendMessage: () => {}}}; // USO bug in FF
const EXT_ID = 'fjnbnpbmkenffdnngjfgmeleoegfcffe';
const {defineProperty} = Object;
const {dispatchEvent, CustomEvent, removeEventListener} = window; const {dispatchEvent, CustomEvent, removeEventListener} = window;
const apply = Map.call.bind(Map.apply); const apply = Map.call.bind(Map.apply);
const CR = chrome.runtime; const OVR = [
const SEND = 'sendMessage'; [chrome.runtime, 'sendMessage', (fn, me, args) => {
const RP = Response.prototype; const [id, /*msg*/, opts, cb = opts] = args;
const ORIG = {json: RP.json, [SEND]: CR[SEND]}; if (id !== EXT_ID) return apply(fn, me, args);
let done, orphaned, vars; if (typeof cb !== 'function') return Promise.resolve(true);
CR[SEND] = ovrSend;
RP.json = ovrJson;
window.isInstalled = true;
addEventListener(eventId, onCommand, true);
function ovrSend(id, msg, opts, cb = opts) {
if (!orphaned &&
id === 'fjnbnpbmkenffdnngjfgmeleoegfcffe' &&
msg && msg.type === 'deleteStyle' &&
typeof cb === 'function') {
cb(true); cb(true);
} else { }],
return ORIG[SEND](...arguments); [Response.prototype, 'json', async (fn, me, args) => {
} const res = await apply(fn, me, args);
}
async function ovrJson() {
const res = await apply(ORIG.json, this, arguments);
try { try {
if (!done && this.url === apiUrl) { if (!done && me.url === apiUrl) {
if (RP.json === ovrJson) RP.json = ORIG.json;
done = true; done = true;
send(res); send(res);
setVars(res); setVars(res);
} }
} catch (e) {} } catch (e) {}
return res; return res;
} }],
[window, 'fetch', (fn, me, args) =>
args[0] === `chrome-extension://${EXT_ID}/index.html`
? Promise.resolve(new Response('<!doctype html><html lang="en"></html>'))
: apply(fn, me, args),
],
];
OVR.forEach(([obj, name, caller], i) => {
/* Using Proxy to make the override undetectable so Stylish cannot track our users,
* which was the primary reason privacy-concerned users abandoned Stylish.
* TODO: add a user option to allow USO see the user has Stylus? */
const orig = obj[name];
const ovr = new Proxy(orig, {
apply(fn, me, args) {
if (orphaned) restore(obj, name, ovr, fn);
return (orphaned ? apply : caller)(fn, me, args);
},
});
defineProperty(obj, name, {value: ovr});
OVR[i] = [obj, name, ovr, orig]; // same args as restore()
});
window.isInstalled = true;
addEventListener(eventId, onCommand, true);
function onCommand(e) { function onCommand(e) {
if (e.detail === 'quit') { if (e.detail === 'quit') {
removeEventListener(eventId, onCommand, true); removeEventListener(eventId, onCommand, true);
// We can restore the hooks only if another script didn't modify them OVR.forEach(restore);
if (CR[SEND] === ovrSend) CR[SEND] = ovrSend;
if (RP.json === ovrJson) RP.json = ORIG.json;
done = orphaned = true; done = orphaned = true;
} else if (/^vars:/.test(e.detail)) { } else if (/^vars:/.test(e.detail)) {
vars = JSON.parse(e.detail.slice(5)); vars = JSON.parse(e.detail.slice(5));
@ -231,6 +250,11 @@ function inPageContext(eventId, eventIdHost, styleId, apiUrl) {
send(e.relatedTarget.uploadedData); send(e.relatedTarget.uploadedData);
} }
} }
function restore(obj, name, ovr, orig) { // same order as OVR after patching
if (obj[name] === ovr) {
defineProperty(obj, name, {value: orig});
}
}
function send(data) { function send(data) {
dispatchEvent(new CustomEvent(eventIdHost, {__proto: null, detail: data})); dispatchEvent(new CustomEvent(eventIdHost, {__proto: null, detail: data}));
} }
@ -261,7 +285,7 @@ function inPageContext(eventId, eventIdHost, styleId, apiUrl) {
if (ss.setting_type === 'image') { if (ss.setting_type === 'image') {
let isListed; let isListed;
for (const opt of ss.style_setting_options) { for (const opt of ss.style_setting_options) {
isListed |= opt.default = (opt.value === value); isListed |= opt.default = (opt.install_key === value);
} }
images.set(ik, {url: isNew && !isListed ? vars[`${ik}-custom`].value : value, isListed}); images.set(ik, {url: isNew && !isListed ? vars[`${ik}-custom`].value : value, isListed});
} else if (value.startsWith('ik-') || isNew && vars[ik].type === 'select') { } else if (value.startsWith('ik-') || isNew && vars[ik].type === 'select') {