protect own style elements (100 times max to avoid deadlocks)

fixes #252
This commit is contained in:
tophf 2017-11-21 09:45:44 +03:00
parent 5d905c2952
commit 8a1908b760

View File

@ -224,8 +224,12 @@ function applyStyles(styles) {
for (const id in styles) {
applySections(id, styles[id].map(section => section.code).join('\n'));
}
if (!isOwnPage && !docRewriteObserver && styleElements.size) {
initDocRewriteObserver();
}
if (!docRootObserver && styleElements.size) {
initDocRootObserver();
}
if (retiredStyleTimers.size) {
setTimeout(() => {
for (const [id, timer] of retiredStyleTimers.entries()) {
@ -290,9 +294,6 @@ function replaceAll(newStyles) {
function initDocRewriteObserver() {
if (isOwnPage || docRewriteObserver || !styleElements.size) {
return;
}
// re-add styles if we detect documentElement being recreated
const reinjectStyles = () => {
if (!styleElements) {
@ -327,36 +328,57 @@ function initDocRewriteObserver() {
function initDocRootObserver() {
if (!styleElements.size || document.body || docRootObserver) {
return;
let lastRestorationTime = 0;
let restorationCounter = 0;
docRootObserver = new MutationObserver(findMisplacedStyles);
connectObserver();
function connectObserver() {
docRootObserver.observe(ROOT, {childList: true});
}
// wait for BODY and move all style elements after it
docRootObserver = new MutationObserver(() => {
function findMisplacedStyles() {
let expectedPrevSibling = document.body || document.head;
if (!expectedPrevSibling) {
return;
}
docRootObserver.disconnect();
const list = [];
for (const el of styleElements.values()) {
if (el.previousElementSibling !== expectedPrevSibling) {
ROOT.insertBefore(el, expectedPrevSibling.nextSibling);
list.push({el, before: expectedPrevSibling.nextSibling});
}
expectedPrevSibling = el;
}
if (list.length && !restorationLimitExceeded()) {
restoreMisplacedStyles(list);
}
}
function restoreMisplacedStyles(list) {
docRootObserver.disconnect();
for (const {el, before} of list) {
ROOT.insertBefore(el, before);
if (el.disabled !== disableAll) {
// moving an element resets its 'disabled' state
el.disabled = disableAll;
}
}
expectedPrevSibling = el;
connectObserver();
}
function restorationLimitExceeded() {
const t = performance.now();
if (t - lastRestorationTime > 1000) {
restorationCounter = 0;
}
lastRestorationTime = t;
if (++restorationCounter > 100) {
console.error('Stylus stopped restoring userstyle elements after 100 failed attempts.\n' +
'Please report on https://github.com/openstyles/stylus/issues');
return true;
}
if (document.body) {
docRootObserver = null;
} else {
docRootObserver.connect();
}
});
docRootObserver.connect = () => {
docRootObserver.observe(ROOT, {childList: true});
};
docRootObserver.connect();
}