simplify orphanCheck; use IIFE; fix comm issues
This commit is contained in:
parent
babeb695c1
commit
fb3554a351
163
content/apply.js
163
content/apply.js
|
@ -1,6 +1,7 @@
|
||||||
/* eslint no-var: 0 */
|
/* eslint no-var: 0 */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
(() => {
|
||||||
var ID_PREFIX = 'stylus-';
|
var ID_PREFIX = 'stylus-';
|
||||||
var ROOT = document.documentElement;
|
var ROOT = document.documentElement;
|
||||||
var isOwnPage = location.protocol.endsWith('-extension:');
|
var isOwnPage = location.protocol.endsWith('-extension:');
|
||||||
|
@ -14,6 +15,7 @@ var docRootObserver;
|
||||||
|
|
||||||
requestStyles();
|
requestStyles();
|
||||||
chrome.runtime.onMessage.addListener(applyOnMessage);
|
chrome.runtime.onMessage.addListener(applyOnMessage);
|
||||||
|
window.applyOnMessage = applyOnMessage;
|
||||||
|
|
||||||
if (!isOwnPage) {
|
if (!isOwnPage) {
|
||||||
window.dispatchEvent(new CustomEvent(chrome.runtime.id));
|
window.dispatchEvent(new CustomEvent(chrome.runtime.id));
|
||||||
|
@ -257,10 +259,9 @@ function applyStyles(styles) {
|
||||||
|
|
||||||
|
|
||||||
function applySections(styleId, code) {
|
function applySections(styleId, code) {
|
||||||
let el = document.getElementById(ID_PREFIX + styleId);
|
const id = ID_PREFIX + styleId;
|
||||||
if (el) {
|
let el = styleElements.get(id) || document.getElementById(id);
|
||||||
return;
|
if (!el) {
|
||||||
}
|
|
||||||
if (document.documentElement instanceof SVGSVGElement) {
|
if (document.documentElement instanceof SVGSVGElement) {
|
||||||
// SVG document style
|
// SVG document style
|
||||||
el = document.createElementNS('http://www.w3.org/2000/svg', 'style');
|
el = document.createElementNS('http://www.w3.org/2000/svg', 'style');
|
||||||
|
@ -272,15 +273,15 @@ function applySections(styleId, code) {
|
||||||
el = document.createElement('style');
|
el = document.createElement('style');
|
||||||
}
|
}
|
||||||
Object.assign(el, {
|
Object.assign(el, {
|
||||||
styleId,
|
id,
|
||||||
id: ID_PREFIX + styleId,
|
|
||||||
type: 'text/css',
|
type: 'text/css',
|
||||||
textContent: code,
|
textContent: code,
|
||||||
});
|
});
|
||||||
// SVG className is not a string, but an instance of SVGAnimatedString
|
// SVG className is not a string, but an instance of SVGAnimatedString
|
||||||
el.classList.add('stylus');
|
el.classList.add('stylus');
|
||||||
addStyleElement(el);
|
addStyleElement(el);
|
||||||
styleElements.set(el.id, el);
|
}
|
||||||
|
styleElements.set(id, el);
|
||||||
disabledElements.delete(Number(styleId));
|
disabledElements.delete(Number(styleId));
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
@ -323,6 +324,22 @@ function replaceAll(newStyles) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getStyleId(el) {
|
||||||
|
return parseInt(el.id.substr(ID_PREFIX.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function orphanCheck() {
|
||||||
|
if (chrome.i18n && chrome.i18n.getUILanguage()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// In Chrome content script is orphaned on an extension update/reload
|
||||||
|
// so we need to detach event listeners
|
||||||
|
[docRewriteObserver, docRootObserver].forEach(ob => ob && ob.takeRecords() && ob.disconnect());
|
||||||
|
window.removeEventListener(chrome.runtime.id, orphanCheck, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function initDocRewriteObserver() {
|
function initDocRewriteObserver() {
|
||||||
// detect documentElement being rewritten from inside the script
|
// detect documentElement being rewritten from inside the script
|
||||||
docRewriteObserver = new MutationObserver(mutations => {
|
docRewriteObserver = new MutationObserver(mutations => {
|
||||||
|
@ -346,9 +363,7 @@ function initDocRewriteObserver() {
|
||||||
// re-add styles if we detect documentElement being recreated
|
// re-add styles if we detect documentElement being recreated
|
||||||
function reinjectStyles() {
|
function reinjectStyles() {
|
||||||
if (!styleElements) {
|
if (!styleElements) {
|
||||||
if (orphanCheck) {
|
|
||||||
orphanCheck();
|
orphanCheck();
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ROOT = document.documentElement;
|
ROOT = document.documentElement;
|
||||||
|
@ -370,26 +385,32 @@ function initDocRootObserver() {
|
||||||
let lastRestorationTime = 0;
|
let lastRestorationTime = 0;
|
||||||
let restorationCounter = 0;
|
let restorationCounter = 0;
|
||||||
let observing = false;
|
let observing = false;
|
||||||
|
let sorting = false;
|
||||||
|
// allow any types of elements between ours, except for the following:
|
||||||
|
const ORDERED_TAGS = ['head', 'body', 'style', 'link'];
|
||||||
|
|
||||||
docRootObserver = Object.assign(new MutationObserver(sortStyleElements), {
|
init();
|
||||||
start({sort = false} = {}) {
|
return;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
docRootObserver = new MutationObserver(sortStyleElements);
|
||||||
|
Object.assign(docRootObserver, {start, stop});
|
||||||
|
}
|
||||||
|
function start({sort = false} = {}) {
|
||||||
if (sort && sortStyleMap()) {
|
if (sort && sortStyleMap()) {
|
||||||
sortStyleElements();
|
sortStyleElements();
|
||||||
}
|
}
|
||||||
if (!observing) {
|
if (!observing && ROOT) {
|
||||||
this.observe(ROOT, {childList: true});
|
docRootObserver.observe(ROOT, {childList: true});
|
||||||
observing = true;
|
observing = true;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
stop() {
|
function stop() {
|
||||||
if (observing) {
|
if (observing) {
|
||||||
this.disconnect();
|
docRootObserver.disconnect();
|
||||||
observing = false;
|
observing = false;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
|
||||||
return;
|
|
||||||
|
|
||||||
function sortStyleMap() {
|
function sortStyleMap() {
|
||||||
const list = [];
|
const list = [];
|
||||||
let prevStyleId = 0;
|
let prevStyleId = 0;
|
||||||
|
@ -407,40 +428,60 @@ function initDocRootObserver() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortStyleElements() {
|
function sortStyleElements() {
|
||||||
let prev = document.body || document.head;
|
let expected = document.body || document.head;
|
||||||
if (!prev) {
|
if (!expected || sorting) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let appliedChanges = false;
|
for (const el of styleElements.values()) {
|
||||||
for (const [idStr, el] of styleElements.entries()) {
|
if (!isMovable(el)) {
|
||||||
if (!el.parentNode && disabledElements.has(getStyleId(idStr))) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (el.previousElementSibling === prev) {
|
let prev = el.previousElementSibling;
|
||||||
prev = el;
|
while (prev !== expected) {
|
||||||
continue;
|
if (prev && isSkippable(prev)) {
|
||||||
|
expected = prev;
|
||||||
|
prev = prev.nextElementSibling;
|
||||||
|
} else if (!moveAfter(el, expected)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (!appliedChanges) {
|
}
|
||||||
|
expected = el;
|
||||||
|
}
|
||||||
|
if (sorting) {
|
||||||
|
sorting = false;
|
||||||
|
docRootObserver.takeRecords();
|
||||||
|
setTimeout(start);
|
||||||
|
//docRootObserver.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function isMovable(el) {
|
||||||
|
return el.parentNode || !disabledElements.has(getStyleId(el));
|
||||||
|
}
|
||||||
|
function isSkippable(el) {
|
||||||
|
return !ORDERED_TAGS.includes(el.localName) ||
|
||||||
|
el.id.startsWith(ID_PREFIX) &&
|
||||||
|
el.id.endsWith('-ghost') &&
|
||||||
|
el.localName === 'style' &&
|
||||||
|
el.className === 'stylus';
|
||||||
|
}
|
||||||
|
function moveAfter(el, expected) {
|
||||||
|
if (!sorting) {
|
||||||
if (restorationLimitExceeded()) {
|
if (restorationLimitExceeded()) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
appliedChanges = true;
|
sorting = true;
|
||||||
docRootObserver.stop();
|
docRootObserver.stop();
|
||||||
}
|
}
|
||||||
prev.insertAdjacentElement('afterend', el);
|
expected.insertAdjacentElement('afterend', el);
|
||||||
if (el.disabled !== disableAll) {
|
if (el.disabled !== disableAll) {
|
||||||
// moving an element resets its 'disabled' state
|
// moving an element resets its 'disabled' state
|
||||||
el.disabled = disableAll;
|
el.disabled = disableAll;
|
||||||
}
|
}
|
||||||
prev = el;
|
return true;
|
||||||
}
|
}
|
||||||
if (appliedChanges) {
|
|
||||||
docRootObserver.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function restorationLimitExceeded() {
|
function restorationLimitExceeded() {
|
||||||
const t = performance.now();
|
const t = performance.now();
|
||||||
if (t - lastRestorationTime > 1000) {
|
if (t - lastRestorationTime > 1000) {
|
||||||
|
@ -454,46 +495,4 @@ function initDocRootObserver() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
function getStyleId(el) {
|
|
||||||
return parseInt((el.id || el).substr(ID_PREFIX.length));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function orphanCheck() {
|
|
||||||
const port = chrome.runtime.connect();
|
|
||||||
if (port) {
|
|
||||||
port.disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we're orphaned due to an extension update
|
|
||||||
// we can detach the mutation observer
|
|
||||||
[docRewriteObserver, docRootObserver].forEach(ob => ob && ob.disconnect());
|
|
||||||
// we can detach event listeners
|
|
||||||
window.removeEventListener(chrome.runtime.id, orphanCheck, true);
|
|
||||||
// we can't detach chrome.runtime.onMessage because it's no longer connected internally
|
|
||||||
// we can destroy our globals in this context to free up memory
|
|
||||||
[ // functions
|
|
||||||
'addStyleElement',
|
|
||||||
'applyOnMessage',
|
|
||||||
'applySections',
|
|
||||||
'applyStyles',
|
|
||||||
'applyStyleState',
|
|
||||||
'doDisableAll',
|
|
||||||
'initDocRewriteObserver',
|
|
||||||
'initDocRootObserver',
|
|
||||||
'orphanCheck',
|
|
||||||
'removeStyle',
|
|
||||||
'replaceAll',
|
|
||||||
'requestStyles',
|
|
||||||
// variables
|
|
||||||
'ROOT',
|
|
||||||
'disabledElements',
|
|
||||||
'retiredStyleTimers',
|
|
||||||
'styleElements',
|
|
||||||
'docRewriteObserver',
|
|
||||||
'docRootObserver',
|
|
||||||
].forEach(fn => (window[fn] = null));
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
(() => {
|
||||||
const FIREFOX = !chrome.app;
|
const FIREFOX = !chrome.app;
|
||||||
const VIVALDI = chrome.app && /Vivaldi/.test(navigator.userAgent);
|
const VIVALDI = chrome.app && /Vivaldi/.test(navigator.userAgent);
|
||||||
const OPERA = chrome.app && /OPR/.test(navigator.userAgent);
|
const OPERA = chrome.app && /OPR/.test(navigator.userAgent);
|
||||||
|
@ -18,99 +19,6 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: remove the following statement when USO is fixed
|
|
||||||
document.documentElement.appendChild(document.createElement('script')).text = '(' +
|
|
||||||
function () {
|
|
||||||
let settings;
|
|
||||||
const originalResponseJson = Response.prototype.json;
|
|
||||||
document.addEventListener('stylusFixBuggyUSOsettings', function _({detail}) {
|
|
||||||
document.removeEventListener('stylusFixBuggyUSOsettings', _);
|
|
||||||
settings = /\?/.test(detail) && new URLSearchParams(new URL(detail).search);
|
|
||||||
if (!settings) {
|
|
||||||
Response.prototype.json = originalResponseJson;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Response.prototype.json = function (...args) {
|
|
||||||
return originalResponseJson.call(this, ...args).then(json => {
|
|
||||||
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();
|
|
||||||
for (const [name, url] of images.entries()) {
|
|
||||||
const elRadio = document.querySelector(`input[name="ik-${name}"][value="user-url"]`);
|
|
||||||
const elUrl = elRadio && document.getElementById(elRadio.id.replace('url-choice', 'user-url'));
|
|
||||||
if (elUrl) {
|
|
||||||
elUrl.value = url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).observe(document, {childList: true, subtree: true});
|
|
||||||
}
|
|
||||||
return json;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
} + ')()';
|
|
||||||
|
|
||||||
// TODO: remove the following statement when USO pagination is fixed
|
|
||||||
if (location.search.includes('category=')) {
|
|
||||||
document.addEventListener('DOMContentLoaded', function _() {
|
|
||||||
document.removeEventListener('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});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
new MutationObserver((mutations, observer) => {
|
new MutationObserver((mutations, observer) => {
|
||||||
if (document.body) {
|
if (document.body) {
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
|
@ -188,7 +96,7 @@ function sendEvent(type, detail = null) {
|
||||||
|
|
||||||
|
|
||||||
function onClick(event) {
|
function onClick(event) {
|
||||||
if (onClick.processing) {
|
if (onClick.processing || !orphanCheck()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onClick.processing = true;
|
onClick.processing = true;
|
||||||
|
@ -352,15 +260,108 @@ function onDOMready() {
|
||||||
|
|
||||||
|
|
||||||
function orphanCheck() {
|
function orphanCheck() {
|
||||||
const port = chrome.runtime.connect();
|
if (chrome.i18n && chrome.i18n.getUILanguage()) {
|
||||||
if (port) {
|
|
||||||
port.disconnect();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// we're orphaned due to an extension update
|
// In Chrome content script is orphaned on an extension update/reload
|
||||||
// we can 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);
|
||||||
['Update', 'Install'].forEach(type =>
|
['Update', 'Install'].forEach(type =>
|
||||||
['', 'Chrome', 'Opera'].forEach(browser =>
|
['', 'Chrome', 'Opera'].forEach(browser =>
|
||||||
document.addEventListener('stylish' + type + browser, onClick)));
|
document.addEventListener('stylish' + type + browser, onClick)));
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// TODO: remove the following statement when USO is fixed
|
||||||
|
document.documentElement.appendChild(document.createElement('script')).text = '(' +
|
||||||
|
function () {
|
||||||
|
let settings;
|
||||||
|
const originalResponseJson = Response.prototype.json;
|
||||||
|
document.currentScript.remove();
|
||||||
|
document.addEventListener('stylusFixBuggyUSOsettings', function _({detail}) {
|
||||||
|
document.removeEventListener('stylusFixBuggyUSOsettings', _);
|
||||||
|
settings = /\?/.test(detail) && new URLSearchParams(new URL(detail).search);
|
||||||
|
if (!settings) {
|
||||||
|
Response.prototype.json = originalResponseJson;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Response.prototype.json = function (...args) {
|
||||||
|
return originalResponseJson.call(this, ...args).then(json => {
|
||||||
|
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();
|
||||||
|
for (const [name, url] of images.entries()) {
|
||||||
|
const elRadio = document.querySelector(`input[name="ik-${name}"][value="user-url"]`);
|
||||||
|
const elUrl = elRadio && document.getElementById(elRadio.id.replace('url-choice', 'user-url'));
|
||||||
|
if (elUrl) {
|
||||||
|
elUrl.value = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).observe(document, {childList: true, subtree: true});
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
} + ')()';
|
||||||
|
|
||||||
|
// TODO: remove the following statement when USO pagination is fixed
|
||||||
|
if (location.search.includes('category=')) {
|
||||||
|
document.addEventListener('DOMContentLoaded', function _() {
|
||||||
|
document.removeEventListener('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});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user