always sort applied style elements by id

This commit is contained in:
tophf 2017-11-22 20:19:01 +03:00
parent 607160655d
commit c8b6672d79

View File

@ -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) {