TEMPORARY: restore style elements removed by hostile sites

This commit is contained in:
tophf 2017-03-28 07:08:37 +03:00
parent 1c2c14d231
commit e4c8ff9ff1

148
apply.js
View File

@ -7,8 +7,11 @@ 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 orphanCheckTimer;
initObserver(); initObserver();
initStyleObserver();
requestStyles(); requestStyles();
chrome.runtime.onMessage.addListener(applyOnMessage); chrome.runtime.onMessage.addListener(applyOnMessage);
@ -134,10 +137,7 @@ function applyStyleState(id, enabled, doc) {
function removeStyle(id, doc) { function removeStyle(id, doc) {
styleElements.delete('stylus-' + id); styleElements.delete('stylus-' + id);
const el = doc.getElementById('stylus-' + id); removeStyleElements([doc.getElementById('stylus-' + id)]);
if (el) {
el.remove();
}
if (doc == document && !styleElements.size) { if (doc == document && !styleElements.size) {
iframeObserver.disconnect(); iframeObserver.disconnect();
} }
@ -193,6 +193,24 @@ function applyStyles(styleHash) {
} else { } else {
document.addEventListener('DOMContentLoaded', onDOMContentLoaded); document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
} }
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) {
@ -259,6 +277,7 @@ function addDocumentStylesToIFrame(iframe) {
addStyleElement(el, doc); addStyleElement(el, doc);
} }
} }
initStyleObserver(doc);
} }
@ -327,16 +346,38 @@ 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);
oldStyles.forEach(style => style.remove()); removeStyleElements(oldStyles);
}
function removeStyleElements(elements) {
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 // Observe dynamic IFRAMEs being added
function initObserver() { function initObserver() {
let orphanCheckTimer;
const iframesCollection = document.getElementsByTagName('iframe'); const iframesCollection = document.getElementsByTagName('iframe');
iframeObserver = new MutationObserver(function(mutations) { iframeObserver = Object.assign(new MutationObserver(observer), {
start() {
this.observe(document, {childList: true, subtree: true});
}
});
function observer(mutations) {
// MutationObserver runs as a microtask so the timer won't fire // MutationObserver runs as a microtask so the timer won't fire
// until all queued mutations are fired // until all queued mutations are fired
clearTimeout(orphanCheckTimer); clearTimeout(orphanCheckTimer);
@ -355,15 +396,16 @@ function initObserver() {
// because some same-domain (!) iframes fail to load when their 'contentDocument' is accessed (!) // because some same-domain (!) iframes fail to load when their 'contentDocument' is accessed (!)
// namely gmail's old chat iframe talkgadget.google.com // namely gmail's old chat iframe talkgadget.google.com
setTimeout(process, 0, mutations); setTimeout(process, 0, mutations);
}); }
function process(mutations) { function process(mutations) {
/* eslint-disable no-var # var is slightly faster and MutationObserver may run a lot */
for (var m = 0, mutation; (mutation = mutations[m++]);) { for (var m = 0, mutation; (mutation = mutations[m++]);) {
var added = mutation.addedNodes; var added = mutation.addedNodes;
for (var n = 0, node; (node = added[n++]);) { for (var n = 0, node; (node = added[n++]);) {
// process only ELEMENT_NODE // process only ELEMENT_NODE
if (node.nodeType == 1) { if (node.nodeType != 1) {
continue;
}
var iframes = node.localName === 'iframe' ? [node] : var iframes = node.localName === 'iframe' ? [node] :
node.children.length && node.getElementsByTagName('iframe'); node.children.length && node.getElementsByTagName('iframe');
for (var i = 0, iframe; (iframe = iframes[i++]);) { for (var i = 0, iframe; (iframe = iframes[i++]);) {
@ -374,15 +416,59 @@ function initObserver() {
} }
} }
} }
/* eslint-enable no-var */ }
function initStyleObserver(doc = document) {
const observer = Object.assign(new MutationObserver(styleObserver), {
counters: new Map(),
start() {
this.observe(doc.documentElement, {childList: true});
} }
});
doc[styleObserverSymbol] = observer;
observer.start();
}
iframeObserver.start = () => {
// subsequent calls are ignored if already started observing
iframeObserver.observe(document, {childList: true, subtree: true});
};
function orphanCheck() { function styleObserver(mutations, observer) {
//console.log(location.href,
// [].concat.apply([], mutations.map(m => [...m.addedNodes])),
// [].concat.apply([], mutations.map(m => [...m.removedNodes]))
//);
for (var m = 0, mutation; (mutation = mutations[m++]);) {
var removed = mutation.removedNodes;
for (var n = 0, node; (node = removed[n++]);) {
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;
}
}
}
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);
}
}
}
function orphanCheck() {
orphanCheckTimer = 0; orphanCheckTimer = 0;
const port = chrome.runtime.connect(); const port = chrome.runtime.connect();
if (port) { if (port) {
@ -395,12 +481,23 @@ function initObserver() {
iframeObserver.takeRecords(); iframeObserver.takeRecords();
iframeObserver.disconnect(); iframeObserver.disconnect();
iframeObserver = null; iframeObserver = null;
document[styleObserverSymbol].disconnect();
document[styleObserverSymbol] = null;
(function removeStyleObservers(doc) {
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
// we can destroy our globals in this context to free up memory
// we can destroy global functions in this context to free up memory [ // functions
[
'addDocumentStylesToAllIFrames', 'addDocumentStylesToAllIFrames',
'addDocumentStylesToIFrame', 'addDocumentStylesToIFrame',
'addStyleElement', 'addStyleElement',
@ -414,14 +511,17 @@ function initObserver() {
'iframeIsDynamic', 'iframeIsDynamic',
'iframeIsLoadingSrcDoc', 'iframeIsLoadingSrcDoc',
'initObserver', 'initObserver',
'initStyleObserver',
'orphanCheck',
'removeStyle', 'removeStyle',
'replaceAll', 'replaceAll',
'replaceAllpass2', 'replaceAllpass2',
'requestStyles', 'requestStyles',
'retireStyle' 'retireStyle',
'styleObserver',
// variables
'styleElements',
'iframeObserver',
'retiredStyleIds',
].forEach(fn => (window[fn] = null)); ].forEach(fn => (window[fn] = null));
// we can destroy global variables
styleElements = iframeObserver = retiredStyleIds = null;
}
} }