Reinject styles on document-rewrite
uBlock-extra rewrites html of some sites known to have bad scripts. Such new documentElement doesn't have our styles so we need to detect this case by observing the parent document node non-recursively, meaning we don't add overhead to the normal browsing experience.
This commit is contained in:
parent
e4c8ff9ff1
commit
486d4258d3
148
apply.js
148
apply.js
|
@ -3,15 +3,15 @@
|
||||||
/* eslint no-var: 0 */
|
/* eslint no-var: 0 */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var isOwnPage = location.href.startsWith('chrome-extension:');
|
||||||
var disableAll = false;
|
var disableAll = false;
|
||||||
var styleElements = new Map();
|
var styleElements = new Map();
|
||||||
var retiredStyleIds = [];
|
var retiredStyleIds = [];
|
||||||
var iframeObserver;
|
var iframeObserver;
|
||||||
var styleObserverSymbol = Symbol('Stylus.styleObserver');
|
var docRewriteObserver;
|
||||||
var orphanCheckTimer;
|
var orphanCheckTimer;
|
||||||
|
|
||||||
initObserver();
|
initIFrameObserver();
|
||||||
initStyleObserver();
|
|
||||||
requestStyles();
|
requestStyles();
|
||||||
chrome.runtime.onMessage.addListener(applyOnMessage);
|
chrome.runtime.onMessage.addListener(applyOnMessage);
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ chrome.runtime.onMessage.addListener(applyOnMessage);
|
||||||
function requestStyles(options) {
|
function requestStyles(options) {
|
||||||
// If this is a Stylish page (Edit Style or Manage Styles),
|
// If this is a Stylish page (Edit Style or Manage Styles),
|
||||||
// we'll request the styles directly to minimize delay and flicker,
|
// we'll request the styles directly to minimize delay and flicker,
|
||||||
// unless Chrome still starts up and the background page isn't fully loaded.
|
// unless Chrome is still starting up and the background page isn't fully loaded.
|
||||||
// (Note: in this case the function may be invoked again from applyStyles.)
|
// (Note: in this case the function may be invoked again from applyStyles.)
|
||||||
const request = Object.assign({
|
const request = Object.assign({
|
||||||
method: 'getStyles',
|
method: 'getStyles',
|
||||||
|
@ -137,7 +137,7 @@ function applyStyleState(id, enabled, doc) {
|
||||||
|
|
||||||
function removeStyle(id, doc) {
|
function removeStyle(id, doc) {
|
||||||
styleElements.delete('stylus-' + id);
|
styleElements.delete('stylus-' + id);
|
||||||
removeStyleElements([doc.getElementById('stylus-' + id)]);
|
[doc.getElementById('stylus-' + id)].forEach(e => e && e.remove());
|
||||||
if (doc == document && !styleElements.size) {
|
if (doc == document && !styleElements.size) {
|
||||||
iframeObserver.disconnect();
|
iframeObserver.disconnect();
|
||||||
}
|
}
|
||||||
|
@ -193,24 +193,7 @@ function applyStyles(styleHash) {
|
||||||
} else {
|
} else {
|
||||||
document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
|
document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
|
||||||
}
|
}
|
||||||
|
initDocRewriteObserver();
|
||||||
if (!location.href.startsWith('chrome-extension:')) {
|
|
||||||
const t0 = performance.now();
|
|
||||||
let counter = 0;
|
|
||||||
console.warn(location.href, 'START');
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
counter++;
|
|
||||||
for (const [id, el] of styleElements.entries()) {
|
|
||||||
if (!document.getElementById(id)) {
|
|
||||||
document.documentElement.appendChild(el);
|
|
||||||
console.log(location.href, el);
|
|
||||||
} else if (performance.now() - t0 > 1000) {
|
|
||||||
console.warn(location.href, 'watchdog fired', counter, 'times');
|
|
||||||
clearInterval(interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 10);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retiredStyleIds.length) {
|
if (retiredStyleIds.length) {
|
||||||
|
@ -277,7 +260,7 @@ function addDocumentStylesToIFrame(iframe) {
|
||||||
addStyleElement(el, doc);
|
addStyleElement(el, doc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initStyleObserver(doc);
|
initDocRewriteObserver(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -285,6 +268,7 @@ function addDocumentStylesToAllIFrames() {
|
||||||
getDynamicIFrames(document).forEach(addDocumentStylesToIFrame);
|
getDynamicIFrames(document).forEach(addDocumentStylesToIFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Only dynamic iframes get the parent document's styles. Other ones should get styles based on their own URLs.
|
// Only dynamic iframes get the parent document's styles. Other ones should get styles based on their own URLs.
|
||||||
function getDynamicIFrames(doc) {
|
function getDynamicIFrames(doc) {
|
||||||
return [...doc.getElementsByTagName('iframe')].filter(iframeIsDynamic);
|
return [...doc.getElementsByTagName('iframe')].filter(iframeIsDynamic);
|
||||||
|
@ -346,42 +330,23 @@ function replaceAll(newStyles, doc) {
|
||||||
function replaceAllpass2(newStyles, doc) {
|
function replaceAllpass2(newStyles, doc) {
|
||||||
const oldStyles = [...doc.querySelectorAll('STYLE.stylus[id$="-ghost"]')];
|
const oldStyles = [...doc.querySelectorAll('STYLE.stylus[id$="-ghost"]')];
|
||||||
processDynamicIFrames(doc, replaceAllpass2, newStyles);
|
processDynamicIFrames(doc, replaceAllpass2, newStyles);
|
||||||
removeStyleElements(oldStyles);
|
oldStyles.forEach(e => e.remove);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function removeStyleElements(elements) {
|
function initIFrameObserver() {
|
||||||
if (!elements[0]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const styleObserver = elements[0].ownerDocument[styleObserverSymbol];
|
|
||||||
if (styleObserver) {
|
|
||||||
styleObserver.disconnect();
|
|
||||||
}
|
|
||||||
for (const el of elements) {
|
|
||||||
el.remove();
|
|
||||||
}
|
|
||||||
if (styleObserver) {
|
|
||||||
styleObserver.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Observe dynamic IFRAMEs being added
|
|
||||||
function initObserver() {
|
|
||||||
const iframesCollection = document.getElementsByTagName('iframe');
|
|
||||||
|
|
||||||
iframeObserver = Object.assign(new MutationObserver(observer), {
|
iframeObserver = Object.assign(new MutationObserver(observer), {
|
||||||
start() {
|
start() {
|
||||||
this.observe(document, {childList: true, subtree: true});
|
this.observe(document, {childList: true, subtree: true});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const iframesCollection = document.getElementsByTagName('iframe');
|
||||||
|
|
||||||
function observer(mutations) {
|
function observer(mutations) {
|
||||||
// MutationObserver runs as a microtask so the timer won't fire
|
if (!isOwnPage) {
|
||||||
// until all queued mutations are fired
|
clearTimeout(orphanCheckTimer);
|
||||||
clearTimeout(orphanCheckTimer);
|
orphanCheckTimer = setTimeout(orphanCheck, 1000);
|
||||||
orphanCheckTimer = setTimeout(orphanCheck, 0);
|
}
|
||||||
// autoupdated HTMLCollection is superfast
|
// autoupdated HTMLCollection is superfast
|
||||||
if (!iframesCollection[0]) {
|
if (!iframesCollection[0]) {
|
||||||
return;
|
return;
|
||||||
|
@ -419,50 +384,28 @@ function initObserver() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function initStyleObserver(doc = document) {
|
function initDocRewriteObserver() {
|
||||||
const observer = Object.assign(new MutationObserver(styleObserver), {
|
if (isOwnPage) {
|
||||||
counters: new Map(),
|
return;
|
||||||
start() {
|
}
|
||||||
this.observe(doc.documentElement, {childList: true});
|
// re-add styles if we detect documentElement being recreated
|
||||||
}
|
docRewriteObserver = new MutationObserver(observer);
|
||||||
});
|
docRewriteObserver.observe(document, {childList: true});
|
||||||
doc[styleObserverSymbol] = observer;
|
|
||||||
observer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
function observer(mutations) {
|
||||||
function styleObserver(mutations, observer) {
|
for (const mutation of mutations) {
|
||||||
//console.log(location.href,
|
for (const node of mutation.addedNodes) {
|
||||||
// [].concat.apply([], mutations.map(m => [...m.addedNodes])),
|
if (node.localName != 'html') {
|
||||||
// [].concat.apply([], mutations.map(m => [...m.removedNodes]))
|
continue;
|
||||||
//);
|
}
|
||||||
for (var m = 0, mutation; (mutation = mutations[m++]);) {
|
for (const [id, el] of styleElements.entries()) {
|
||||||
var removed = mutation.removedNodes;
|
if (!document.getElementById(id)) {
|
||||||
for (var n = 0, node; (node = removed[n++]);) {
|
document.documentElement.appendChild(el);
|
||||||
let id = node.id;
|
|
||||||
var ourElement = styleElements.get(id);
|
|
||||||
if (!ourElement) {
|
|
||||||
for (const [elId, el] of styleElements.entries()) {
|
|
||||||
if (el == node) {
|
|
||||||
node.id = id = elId;
|
|
||||||
ourElement = el;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (!ourElement) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const counter = observer.counters.get(id) || 0;
|
|
||||||
if (counter > 10) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
observer.counters.set(id, counter + 1);
|
|
||||||
if (ourElement.ownerDocument != node.ownerDocument) {
|
|
||||||
ourElement = node.ownerDocument.importNode(ourElement, true);
|
|
||||||
}
|
|
||||||
node.ownerDocument.documentElement.appendChild(ourElement);
|
|
||||||
//console.log('Restoring style', ourElement);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -481,18 +424,10 @@ function orphanCheck() {
|
||||||
iframeObserver.takeRecords();
|
iframeObserver.takeRecords();
|
||||||
iframeObserver.disconnect();
|
iframeObserver.disconnect();
|
||||||
iframeObserver = null;
|
iframeObserver = null;
|
||||||
document[styleObserverSymbol].disconnect();
|
if (docRewriteObserver) {
|
||||||
document[styleObserverSymbol] = null;
|
docRewriteObserver.disconnect();
|
||||||
(function removeStyleObservers(doc) {
|
docRewriteObserver = null;
|
||||||
getDynamicIFrames(doc).forEach(iframe => {
|
}
|
||||||
const styleObserver = iframe.contentDocument[styleObserverSymbol];
|
|
||||||
if (styleObserver) {
|
|
||||||
styleObserver.disconnect();
|
|
||||||
document[styleObserverSymbol] = null;
|
|
||||||
}
|
|
||||||
removeStyleObservers(iframe.contentDocument);
|
|
||||||
});
|
|
||||||
})(document);
|
|
||||||
// we can detach event listeners
|
// we can detach event listeners
|
||||||
document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
|
document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
|
||||||
// we can't detach chrome.runtime.onMessage because it's no longer connected internally
|
// we can't detach chrome.runtime.onMessage because it's no longer connected internally
|
||||||
|
@ -507,12 +442,12 @@ function orphanCheck() {
|
||||||
'applyStyles',
|
'applyStyles',
|
||||||
'doDisableAll',
|
'doDisableAll',
|
||||||
'getDynamicIFrames',
|
'getDynamicIFrames',
|
||||||
'processDynamicIFrames',
|
|
||||||
'iframeIsDynamic',
|
'iframeIsDynamic',
|
||||||
'iframeIsLoadingSrcDoc',
|
'iframeIsLoadingSrcDoc',
|
||||||
'initObserver',
|
'initDocRewriteObserver',
|
||||||
'initStyleObserver',
|
'initIFrameObserver',
|
||||||
'orphanCheck',
|
'orphanCheck',
|
||||||
|
'processDynamicIFrames',
|
||||||
'removeStyle',
|
'removeStyle',
|
||||||
'replaceAll',
|
'replaceAll',
|
||||||
'replaceAllpass2',
|
'replaceAllpass2',
|
||||||
|
@ -520,8 +455,9 @@ function orphanCheck() {
|
||||||
'retireStyle',
|
'retireStyle',
|
||||||
'styleObserver',
|
'styleObserver',
|
||||||
// variables
|
// variables
|
||||||
'styleElements',
|
'docRewriteObserver',
|
||||||
'iframeObserver',
|
'iframeObserver',
|
||||||
'retiredStyleIds',
|
'retiredStyleIds',
|
||||||
|
'styleElements',
|
||||||
].forEach(fn => (window[fn] = null));
|
].forEach(fn => (window[fn] = null));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user