update USO bug workarounds, remove obsolete ones

This commit is contained in:
tophf 2020-10-12 14:01:01 +03:00
parent f63fb8cc15
commit 85a9f6fb6a

View File

@ -1,7 +1,11 @@
/* global cloneInto msg API */ /* global cloneInto msg API */
'use strict'; 'use strict';
(() => { // eslint-disable-next-line no-unused-expressions
/^\/styles\/(\d+)(\/([^/]*))?([?#].*)?$/.test(location.pathname) && (() => {
const styleId = RegExp.$1;
const pageEventId = `${performance.now()}${Math.random()}`;
window.dispatchEvent(new CustomEvent(chrome.runtime.id + '-install')); window.dispatchEvent(new CustomEvent(chrome.runtime.id + '-install'));
window.addEventListener(chrome.runtime.id + '-install', orphanCheck, true); window.addEventListener(chrome.runtime.id + '-install', orphanCheck, true);
@ -17,35 +21,18 @@
}, '*'); }, '*');
}); });
let gotBody = false;
let currentMd5; let currentMd5;
new MutationObserver(observeDOM).observe(document.documentElement, { const md5Url = getMeta('stylish-md5-url') || `https://update.userstyles.org/${styleId}.md5`;
childList: true, Promise.all([
subtree: true, API.findStyle({md5Url}),
}); getResource(md5Url),
observeDOM(); onDOMready(),
]).then(checkUpdatability);
function observeDOM() { document.documentElement.appendChild(
if (!gotBody) { Object.assign(document.createElement('script'), {
if (!document.body) return; textContent: `(${inPageContext})('${pageEventId}')`,
gotBody = true; }));
// TODO: remove the following statement when USO pagination title is fixed
document.title = document.title.replace(/^(\d+)&\w+=/, '#$1: ');
const md5Url = getMeta('stylish-md5-url') || location.href;
Promise.all([
API.findStyle({md5Url}),
getResource(md5Url)
])
.then(checkUpdatability);
}
if (document.getElementById('install_button')) {
onDOMready().then(() => {
requestAnimationFrame(() => {
sendEvent(sendEvent.lastEvent);
});
});
}
}
function onMessage(msg) { function onMessage(msg) {
switch (msg.method) { switch (msg.method) {
@ -72,7 +59,7 @@
function checkUpdatability([installedStyle, md5]) { function checkUpdatability([installedStyle, md5]) {
// TODO: remove the following statement when USO is fixed // TODO: remove the following statement when USO is fixed
document.dispatchEvent(new CustomEvent('stylusFixBuggyUSOsettings', { document.dispatchEvent(new CustomEvent(pageEventId, {
detail: installedStyle && installedStyle.updateUrl, detail: installedStyle && installedStyle.updateUrl,
})); }));
currentMd5 = md5; currentMd5 = md5;
@ -141,7 +128,6 @@
}); });
} }
function onClick(event) { function onClick(event) {
if (onClick.processing || !orphanCheck()) { if (onClick.processing || !orphanCheck()) {
return; return;
@ -227,13 +213,11 @@
} }
} }
function getMeta(name) { function getMeta(name) {
const e = document.querySelector(`link[rel="${name}"]`); const e = document.querySelector(`link[rel="${name}"]`);
return e ? e.getAttribute('href') : null; return e ? e.getAttribute('href') : null;
} }
function getResource(url, options) { function getResource(url, options) {
if (url.startsWith('#')) { if (url.startsWith('#')) {
return Promise.resolve(document.getElementById(url.slice(1)).textContent); return Promise.resolve(document.getElementById(url.slice(1)).textContent);
@ -280,7 +264,6 @@
.catch(() => null); .catch(() => null);
} }
function styleSectionsEqual({sections: a}, {sections: b}) { function styleSectionsEqual({sections: a}, {sections: b}) {
if (!a || !b) { if (!a || !b) {
return undefined; return undefined;
@ -318,14 +301,12 @@
} }
} }
function onDOMready() { function onDOMready() {
return document.readyState !== 'loading' return document.readyState !== 'loading'
? Promise.resolve() ? Promise.resolve()
: new Promise(resolve => document.addEventListener('DOMContentLoaded', resolve, {once: true})); : new Promise(resolve => document.addEventListener('DOMContentLoaded', resolve, {once: true}));
} }
function openSettings(countdown = 10e3) { function openSettings(countdown = 10e3) {
const button = document.querySelector('.customize_button'); const button = document.querySelector('.customize_button');
if (button) { if (button) {
@ -343,12 +324,12 @@
} }
} }
function orphanCheck() { function orphanCheck() {
// TODO: switch to install-hook-usercss.js impl, and remove explicit orphanCheck() calls try {
if (chrome.i18n && chrome.i18n.getUILanguage()) { if (chrome.i18n.getUILanguage()) {
return true; return true;
} }
} catch (e) {}
// In Chrome content script is orphaned on an extension update/reload // In Chrome content script is orphaned on an extension update/reload
// so we need to detach event listeners // so we need to detach event listeners
window.removeEventListener(chrome.runtime.id + '-install', orphanCheck, true); window.removeEventListener(chrome.runtime.id + '-install', orphanCheck, true);
@ -360,129 +341,56 @@
} }
})(); })();
// run in page context function inPageContext(eventId) {
document.documentElement.appendChild(document.createElement('script')).text = '(' + ( document.currentScript.remove();
() => { const origMethods = {
document.currentScript.remove(); json: Response.prototype.json,
byId: document.getElementById,
// spoof Stylish extension presence in Chrome };
if (window.chrome && chrome.app) { let vars;
const realImage = window.Image; // USO bug workaround: prevent errors in console after install and busy cursor
window.Image = function Image(...args) { document.getElementById = id =>
return new Proxy(new realImage(...args), { origMethods.byId.call(document, id) ||
get(obj, key) { (/^(stylish-code|stylish-installed-style-installed-\w+|post-install-ad|style-install-unknown)$/.test(id)
return obj[key]; ? Object.assign(document.createElement('p'), {className: 'afterdownload-ad'})
}, : null);
set(obj, key, value) { // USO bug workaround: use the actual image data in customized settings
if (key === 'src' && /^chrome-extension:/i.test(value)) { document.addEventListener(eventId, ({detail}) => {
setTimeout(() => typeof obj.onload === 'function' && obj.onload()); vars = /\?/.test(detail) && new URL(detail).searchParams;
} else { if (!vars) Response.prototype.json = origMethods.json;
obj[key] = value; }, {once: true});
} Response.prototype.json = async function () {
return true; const json = await origMethods.json.apply(this, arguments);
}, if (vars && json && Array.isArray(json.style_settings)) {
}); Response.prototype.json = origMethods.json;
}; const images = new Map();
} for (const ss of json.style_settings) {
const value = vars.get('ik-' + ss.install_key);
// USO bug workaround: use the actual style settings in API response if (value && ss.setting_type === 'image' && ss.style_setting_options) {
let settings; let isListed;
const originalResponseJson = Response.prototype.json; for (const opt of ss.style_setting_options) {
document.addEventListener('stylusFixBuggyUSOsettings', ({detail}) => { isListed |= opt.default = (opt.value === value);
settings = /\?/.test(detail) && new URL(detail).searchParams; }
if (!settings) { images.set(ss.install_key, {url: value, isListed});
Response.prototype.json = originalResponseJson; }
} }
}, {once: true}); if (images.size) {
Response.prototype.json = function (...args) { new MutationObserver((_, observer) => {
return originalResponseJson.call(this, ...args).then(json => { if (document.getElementById('style-settings')) {
if (!settings || typeof ((json || {}).style_settings || {}).every !== 'function') {
return json;
}
Response.prototype.json = originalResponseJson;
const images = new Map();
for (const jsonSetting of json.style_settings) {
let value = settings.get('ik-' + jsonSetting.install_key);
if (!value
|| !jsonSetting.style_setting_options
|| !jsonSetting.style_setting_options[0]) {
continue;
}
if (value.startsWith('ik-')) {
value = value.replace(/^ik-/, '');
const defaultItem = jsonSetting.style_setting_options.find(item => item.default);
if (!defaultItem || defaultItem.install_key !== value) {
if (defaultItem) {
defaultItem.default = false;
}
jsonSetting.style_setting_options.some(item => {
if (item.install_key === value) {
item.default = true;
return true;
}
});
}
} else if (jsonSetting.setting_type === 'image') {
jsonSetting.style_setting_options.some(item => {
if (item.default) {
item.default = false;
return true;
}
});
images.set(jsonSetting.install_key, value);
} else {
const item = jsonSetting.style_setting_options[0];
if (item.value !== value && item.install_key === 'placeholder') {
item.value = value;
}
}
}
if (images.size) {
new MutationObserver((_, observer) => {
if (!document.getElementById('style-settings')) {
return;
}
observer.disconnect(); observer.disconnect();
for (const [name, url] of images.entries()) { for (const [name, {url, isListed}] of images) {
const elRadio = document.querySelector(`input[name="ik-${name}"][value="user-url"]`); const elRadio = document.querySelector(`input[name="ik-${name}"][value="user-url"]`);
const elUrl = elRadio && document.getElementById(elRadio.id.replace('url-choice', 'user-url')); const elUrl = elRadio &&
document.getElementById(elRadio.id.replace('url-choice', 'user-url'));
if (elUrl) { if (elUrl) {
elRadio.checked = !isListed;
elUrl.value = url; elUrl.value = url;
} }
} }
}).observe(document, {childList: true, subtree: true}); }
} }).observe(document, {childList: true, subtree: true});
return json;
});
};
}
) + `)('${chrome.runtime.getURL('').slice(0, -1)}')`;
// TODO: remove the following statement when USO pagination is fixed
if (location.search.includes('category=')) {
document.addEventListener('DOMContentLoaded', () => {
new MutationObserver((_, observer) => {
if (!document.getElementById('pagination')) {
return;
} }
observer.disconnect();
const category = '&' + location.search.match(/category=[^&]+/)[0];
const links = document.querySelectorAll('#pagination a[href*="page="]:not([href*="category="])');
for (let i = 0; i < links.length; i++) {
links[i].href += category;
}
}).observe(document, {childList: true, subtree: true});
}, {once: true});
}
if (/^https?:\/\/userstyles\.org\/styles\/\d{3,}/.test(location.href)) {
new MutationObserver((_, observer) => {
const cssButton = document.getElementsByClassName('css_button');
if (cssButton.length) {
// Click on the "Show CSS Code" button to workaround the JS error
cssButton[0].click();
cssButton[0].click();
observer.disconnect();
} }
}).observe(document, {childList: true, subtree: true}); return json;
};
} }