always sort applied style elements by id
This commit is contained in:
parent
607160655d
commit
c8b6672d79
166
content/apply.js
166
content/apply.js
|
@ -204,6 +204,16 @@ function applyStyles(styles) {
|
||||||
doExposeIframes(styles.exposeIframes);
|
doExposeIframes(styles.exposeIframes);
|
||||||
delete styles.exposeIframes;
|
delete styles.exposeIframes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const gotNewStyles = Object.keys(styles).length || styles.needTransitionPatch;
|
||||||
|
if (gotNewStyles) {
|
||||||
|
if (docRootObserver) {
|
||||||
|
docRootObserver.stop();
|
||||||
|
} else {
|
||||||
|
initDocRootObserver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (styles.needTransitionPatch) {
|
if (styles.needTransitionPatch) {
|
||||||
// CSS transition bug workaround: since we insert styles asynchronously,
|
// CSS transition bug workaround: since we insert styles asynchronously,
|
||||||
// the browsers, especially Firefox, may apply all transitions on page load
|
// the browsers, especially Firefox, may apply all transitions on page load
|
||||||
|
@ -221,15 +231,18 @@ function applyStyles(styles) {
|
||||||
document.documentElement.classList.remove(className);
|
document.documentElement.classList.remove(className);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (const id in styles) {
|
|
||||||
applySections(id, styles[id].map(section => section.code).join('\n'));
|
if (gotNewStyles) {
|
||||||
|
for (const id in styles) {
|
||||||
|
applySections(id, styles[id].map(section => section.code).join('\n'));
|
||||||
|
}
|
||||||
|
docRootObserver.start({sort: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isOwnPage && !docRewriteObserver && styleElements.size) {
|
if (!isOwnPage && !docRewriteObserver && styleElements.size) {
|
||||||
initDocRewriteObserver();
|
initDocRewriteObserver();
|
||||||
}
|
}
|
||||||
if (!docRootObserver && styleElements.size) {
|
|
||||||
initDocRootObserver();
|
|
||||||
}
|
|
||||||
if (retiredStyleTimers.size) {
|
if (retiredStyleTimers.size) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
for (const [id, timer] of retiredStyleTimers.entries()) {
|
for (const [id, timer] of retiredStyleTimers.entries()) {
|
||||||
|
@ -257,6 +270,7 @@ function applySections(styleId, code) {
|
||||||
el = document.createElement('style');
|
el = document.createElement('style');
|
||||||
}
|
}
|
||||||
Object.assign(el, {
|
Object.assign(el, {
|
||||||
|
styleId,
|
||||||
id: ID_PREFIX + styleId,
|
id: ID_PREFIX + styleId,
|
||||||
type: 'text/css',
|
type: 'text/css',
|
||||||
textContent: code,
|
textContent: code,
|
||||||
|
@ -270,13 +284,27 @@ function applySections(styleId, code) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function addStyleElement(el) {
|
function addStyleElement(newElement) {
|
||||||
if (ROOT && !document.getElementById(el.id)) {
|
if (!ROOT) {
|
||||||
ROOT.appendChild(el);
|
return;
|
||||||
if (disableAll) {
|
}
|
||||||
el.disabled = true;
|
let next;
|
||||||
|
const newStyleId = getStyleId(newElement);
|
||||||
|
for (const el of styleElements.values()) {
|
||||||
|
if (el.parentNode && !el.id.endsWith('-ghost') && getStyleId(el) > newStyleId) {
|
||||||
|
next = el.parentNode === ROOT ? el : null;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (next === newElement.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
docRootObserver.stop();
|
||||||
|
ROOT.insertBefore(newElement, next || null);
|
||||||
|
if (disableAll) {
|
||||||
|
newElement.disabled = true;
|
||||||
|
}
|
||||||
|
docRootObserver.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -294,17 +322,6 @@ function replaceAll(newStyles) {
|
||||||
|
|
||||||
|
|
||||||
function initDocRewriteObserver() {
|
function initDocRewriteObserver() {
|
||||||
// re-add styles if we detect documentElement being recreated
|
|
||||||
const reinjectStyles = () => {
|
|
||||||
if (!styleElements) {
|
|
||||||
return orphanCheck && orphanCheck();
|
|
||||||
}
|
|
||||||
ROOT = document.documentElement;
|
|
||||||
for (const el of styleElements.values()) {
|
|
||||||
el.textContent += ' '; // invalidate CSSOM cache
|
|
||||||
addStyleElement(document.importNode(el, true));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// detect documentElement being rewritten from inside the script
|
// detect documentElement being rewritten from inside the script
|
||||||
docRewriteObserver = new MutationObserver(mutations => {
|
docRewriteObserver = new MutationObserver(mutations => {
|
||||||
for (let m = mutations.length; --m >= 0;) {
|
for (let m = mutations.length; --m >= 0;) {
|
||||||
|
@ -324,48 +341,102 @@ function initDocRewriteObserver() {
|
||||||
reinjectStyles();
|
reinjectStyles();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// re-add styles if we detect documentElement being recreated
|
||||||
|
function reinjectStyles() {
|
||||||
|
if (!styleElements) {
|
||||||
|
if (orphanCheck) {
|
||||||
|
orphanCheck();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ROOT = document.documentElement;
|
||||||
|
docRootObserver.stop();
|
||||||
|
const imported = [];
|
||||||
|
for (const [id, el] of styleElements.entries()) {
|
||||||
|
const copy = document.importNode(el, true);
|
||||||
|
el.textContent += ' '; // invalidate CSSOM cache
|
||||||
|
imported.push([id, copy]);
|
||||||
|
addStyleElement(copy);
|
||||||
|
}
|
||||||
|
docRootObserver.start();
|
||||||
|
styleElements = new Map(imported);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function initDocRootObserver() {
|
function initDocRootObserver() {
|
||||||
let lastRestorationTime = 0;
|
let lastRestorationTime = 0;
|
||||||
let restorationCounter = 0;
|
let restorationCounter = 0;
|
||||||
|
let observing = false;
|
||||||
|
|
||||||
docRootObserver = new MutationObserver(findMisplacedStyles);
|
docRootObserver = Object.assign(new MutationObserver(sortStyleElements), {
|
||||||
connectObserver();
|
start({sort = false} = {}) {
|
||||||
|
if (sort && sortStyleMap()) {
|
||||||
|
sortStyleElements();
|
||||||
|
}
|
||||||
|
if (!observing) {
|
||||||
|
this.observe(ROOT, {childList: true});
|
||||||
|
observing = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
if (observing) {
|
||||||
|
this.disconnect();
|
||||||
|
observing = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
|
||||||
function connectObserver() {
|
function sortStyleMap() {
|
||||||
docRootObserver.observe(ROOT, {childList: true});
|
const list = [];
|
||||||
|
let prevStyleId = 0;
|
||||||
|
let needsSorting = false;
|
||||||
|
for (const entry of styleElements.entries()) {
|
||||||
|
list.push(entry);
|
||||||
|
const el = entry[1];
|
||||||
|
const styleId = getStyleId(el);
|
||||||
|
el.styleId = styleId;
|
||||||
|
needsSorting |= styleId < prevStyleId;
|
||||||
|
prevStyleId = styleId;
|
||||||
|
}
|
||||||
|
if (needsSorting) {
|
||||||
|
styleElements = new Map(list.sort((a, b) => a[1].styleId - b[1].styleId));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findMisplacedStyles() {
|
function sortStyleElements() {
|
||||||
let expectedPrevSibling = document.body || document.head;
|
let prev = document.body || document.head;
|
||||||
if (!expectedPrevSibling) {
|
if (!prev) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const list = [];
|
let appliedChanges = false;
|
||||||
for (const [id, el] of styleElements.entries()) {
|
for (const el of styleElements.values()) {
|
||||||
if (!disabledElements.has(parseInt(id.substr(ID_PREFIX.length))) &&
|
if (!el.parentNode) {
|
||||||
el.previousElementSibling !== expectedPrevSibling) {
|
continue;
|
||||||
list.push({el, before: expectedPrevSibling.nextSibling});
|
|
||||||
}
|
}
|
||||||
expectedPrevSibling = el;
|
if (el.previousElementSibling === prev) {
|
||||||
}
|
prev = el;
|
||||||
if (list.length && !restorationLimitExceeded()) {
|
continue;
|
||||||
restoreMisplacedStyles(list);
|
}
|
||||||
}
|
if (!appliedChanges) {
|
||||||
}
|
if (restorationLimitExceeded()) {
|
||||||
|
return;
|
||||||
function restoreMisplacedStyles(list) {
|
}
|
||||||
docRootObserver.disconnect();
|
appliedChanges = true;
|
||||||
for (const {el, before} of list) {
|
docRootObserver.stop();
|
||||||
ROOT.insertBefore(el, before);
|
}
|
||||||
|
prev.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;
|
||||||
|
}
|
||||||
|
if (appliedChanges) {
|
||||||
|
docRootObserver.start();
|
||||||
}
|
}
|
||||||
connectObserver();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function restorationLimitExceeded() {
|
function restorationLimitExceeded() {
|
||||||
|
@ -383,6 +454,11 @@ function initDocRootObserver() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getStyleId(el) {
|
||||||
|
return parseInt(el.id.substr(ID_PREFIX.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function orphanCheck() {
|
function orphanCheck() {
|
||||||
const port = chrome.runtime.connect();
|
const port = chrome.runtime.connect();
|
||||||
if (port) {
|
if (port) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user