remove iframe observer
* documentElement may be overwritten right after iframe was initialized with contentDocument.write() and due to this change being external it's not reported in our existing rewrite observer so we enqueue an additional check using setTimeout(0). * match_about_blank in manifest.json is back * iframes with src = about: or javascript: don't have a proper URL when our content script runs so we get the real URL from the parent window * minor refactoring
This commit is contained in:
parent
7ec41bcea1
commit
a93354de8c
464
apply.js
464
apply.js
|
@ -3,11 +3,14 @@
|
||||||
/* eslint no-var: 0 */
|
/* eslint no-var: 0 */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var ID_PREFIX = 'stylus-';
|
||||||
|
var ROOT = document.documentElement;
|
||||||
var isOwnPage = location.href.startsWith('chrome-extension:');
|
var isOwnPage = location.href.startsWith('chrome-extension:');
|
||||||
var disableAll = false;
|
var disableAll = false;
|
||||||
var styleElements = new Map();
|
var styleElements = new Map();
|
||||||
var disabledElements = new Map();
|
var disabledElements = new Map();
|
||||||
var retiredStyleIds = [];
|
var retiredStyleTimers = new Map();
|
||||||
|
var docRewriteObserver;
|
||||||
|
|
||||||
requestStyles();
|
requestStyles();
|
||||||
chrome.runtime.onMessage.addListener(applyOnMessage);
|
chrome.runtime.onMessage.addListener(applyOnMessage);
|
||||||
|
@ -18,16 +21,24 @@ if (!isOwnPage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestStyles(options) {
|
function requestStyles(options) {
|
||||||
|
var matchUrl = location.href;
|
||||||
|
try {
|
||||||
|
// dynamic about: and javascript: iframes don't have an URL yet
|
||||||
|
// so we'll try the parent frame which is guaranteed to have a real URL
|
||||||
|
if (!matchUrl.match(/^(http|file|chrome|ftp)/) && window != parent) {
|
||||||
|
matchUrl = parent.location.href;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
const request = Object.assign({
|
||||||
|
method: 'getStyles',
|
||||||
|
matchUrl,
|
||||||
|
enabled: true,
|
||||||
|
asHash: true,
|
||||||
|
}, 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 is still starting 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({
|
|
||||||
method: 'getStyles',
|
|
||||||
matchUrl: location.href,
|
|
||||||
enabled: true,
|
|
||||||
asHash: true,
|
|
||||||
}, options);
|
|
||||||
if (typeof getStylesSafe !== 'undefined') {
|
if (typeof getStylesSafe !== 'undefined') {
|
||||||
getStylesSafe(request).then(applyStyles);
|
getStylesSafe(request).then(applyStyles);
|
||||||
} else {
|
} else {
|
||||||
|
@ -51,19 +62,19 @@ function applyOnMessage(request, sender, sendResponse) {
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
|
|
||||||
case 'styleDeleted':
|
case 'styleDeleted':
|
||||||
removeStyle(request.id, document);
|
removeStyle(request);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'styleUpdated':
|
case 'styleUpdated':
|
||||||
if (request.codeIsUpdated === false) {
|
if (request.codeIsUpdated === false) {
|
||||||
applyStyleState(request.style.id, request.style.enabled, document);
|
applyStyleState(request.style);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!request.style.enabled) {
|
if (!request.style.enabled) {
|
||||||
removeStyle(request.style.id, document);
|
removeStyle(request.style);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
retireStyle(request.style.id);
|
removeStyle({id: request.style.id, retire: true});
|
||||||
// fallthrough to 'styleAdded'
|
// fallthrough to 'styleAdded'
|
||||||
|
|
||||||
case 'styleAdded':
|
case 'styleAdded':
|
||||||
|
@ -77,7 +88,7 @@ function applyOnMessage(request, sender, sendResponse) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'styleReplaceAll':
|
case 'styleReplaceAll':
|
||||||
replaceAll(request.styles, document);
|
replaceAll(request.styles);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'prefChanged':
|
case 'prefChanged':
|
||||||
|
@ -93,36 +104,23 @@ function applyOnMessage(request, sender, sendResponse) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function doDisableAll(disable, doc = document) {
|
function doDisableAll(disable) {
|
||||||
if (doc == document && !disable === !disableAll) {
|
if (!disable === !disableAll) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
disableAll = disable;
|
disableAll = disable;
|
||||||
if (disable && doc.iframeObserver) {
|
Array.prototype.forEach.call(document.styleSheets, stylesheet => {
|
||||||
doc.iframeObserver.stop();
|
if (stylesheet.ownerNode.matches(`STYLE.stylus[id^="${ID_PREFIX}"]`)
|
||||||
}
|
|
||||||
Array.prototype.forEach.call(doc.styleSheets, stylesheet => {
|
|
||||||
if (stylesheet.ownerNode.matches('stylus[id^="stylus-"]')
|
|
||||||
&& stylesheet.disabled != disable) {
|
&& stylesheet.disabled != disable) {
|
||||||
stylesheet.disabled = disable;
|
stylesheet.disabled = disable;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
for (const iframe of getDynamicIFrames(doc)) {
|
|
||||||
if (!disable) {
|
|
||||||
// update the IFRAME if it was created while the observer was disconnected
|
|
||||||
addDocumentStylesToIFrame(iframe);
|
|
||||||
}
|
|
||||||
doDisableAll(disable, iframe.contentDocument);
|
|
||||||
}
|
|
||||||
if (!disable && doc.readyState != 'loading' && doc.iframeObserver) {
|
|
||||||
doc.iframeObserver.start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function applyStyleState(id, enabled, doc) {
|
function applyStyleState({id, enabled}) {
|
||||||
const inCache = disabledElements.get(id);
|
const inCache = disabledElements.get(id) || styleElements.get(id);
|
||||||
const inDoc = doc.getElementById('stylus-' + id);
|
const inDoc = document.getElementById(ID_PREFIX + id);
|
||||||
if (enabled && inDoc || !enabled && !inDoc) {
|
if (enabled && inDoc || !enabled && !inDoc) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -131,103 +129,80 @@ function applyStyleState(id, enabled, doc) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (enabled && inCache) {
|
if (enabled && inCache) {
|
||||||
const el = inCache.cloneNode(true);
|
addStyleElement(inCache);
|
||||||
doc.documentElement.appendChild(el);
|
|
||||||
el.sheet.disabled = disableAll;
|
|
||||||
processDynamicIFrames(doc, applyStyleState, id, enabled);
|
|
||||||
disabledElements.delete(id);
|
disabledElements.delete(id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!enabled && inDoc) {
|
if (!enabled && inDoc) {
|
||||||
if (!inCache) {
|
disabledElements.set(id, inDoc);
|
||||||
disabledElements.set(id, inDoc);
|
|
||||||
}
|
|
||||||
inDoc.remove();
|
inDoc.remove();
|
||||||
if (doc.location.href == 'about:srcdoc') {
|
if (document.location.href == 'about:srcdoc') {
|
||||||
const original = doc.getElementById('stylus-' + id);
|
const original = document.getElementById(ID_PREFIX + id);
|
||||||
if (original) {
|
if (original) {
|
||||||
original.remove();
|
original.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processDynamicIFrames(doc, applyStyleState, id, enabled);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function removeStyle(id, doc) {
|
function removeStyle({id, retire = false}) {
|
||||||
[doc.getElementById('stylus-' + id)].forEach(e => e && e.remove());
|
const el = document.getElementById(ID_PREFIX + id);
|
||||||
if (doc == document) {
|
if (el) {
|
||||||
styleElements.delete('stylus-' + id);
|
if (retire) {
|
||||||
disabledElements.delete(id);
|
// to avoid page flicker when the style is updated
|
||||||
if (!styleElements.size) {
|
// instead of removing it immediately we rename its ID and queue it
|
||||||
doc.iframeObserver.disconnect();
|
// to be deleted in applyStyles after a new version is fetched and applied
|
||||||
|
const deadID = 'ghost-' + id;
|
||||||
|
el.id = ID_PREFIX + deadID;
|
||||||
|
// in case something went wrong and new style was never applied
|
||||||
|
retiredStyleTimers.set(deadID, setTimeout(removeStyle, 1000, {id: deadID}));
|
||||||
|
} else {
|
||||||
|
el.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processDynamicIFrames(doc, removeStyle, id);
|
styleElements.delete(ID_PREFIX + id);
|
||||||
|
disabledElements.delete(id);
|
||||||
|
retiredStyleTimers.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// to avoid page flicker when the style is updated
|
function applyStyles(styles) {
|
||||||
// instead of removing it immediately we rename its ID and queue it
|
if (!styles) {
|
||||||
// to be deleted in applyStyles after a new version is fetched and applied
|
// Chrome is starting up
|
||||||
function retireStyle(id, doc) {
|
|
||||||
const deadID = 'ghost-' + id;
|
|
||||||
if (!doc) {
|
|
||||||
doc = document;
|
|
||||||
retiredStyleIds.push(deadID);
|
|
||||||
styleElements.delete('stylus-' + id);
|
|
||||||
disabledElements.delete(id);
|
|
||||||
// in case something went wrong and new style was never applied
|
|
||||||
setTimeout(removeStyle, 1000, deadID, doc);
|
|
||||||
}
|
|
||||||
const el = doc.getElementById('stylus-' + id);
|
|
||||||
if (el) {
|
|
||||||
el.id = 'stylus-' + deadID;
|
|
||||||
}
|
|
||||||
processDynamicIFrames(doc, retireStyle, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function applyStyles(styleHash) {
|
|
||||||
if (!styleHash) { // Chrome is starting up
|
|
||||||
requestStyles();
|
requestStyles();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ('disableAll' in styleHash) {
|
if ('disableAll' in styles) {
|
||||||
doDisableAll(styleHash.disableAll);
|
doDisableAll(styles.disableAll);
|
||||||
delete styleHash.disableAll;
|
delete styles.disableAll;
|
||||||
}
|
}
|
||||||
|
if (document.head
|
||||||
for (const styleId in styleHash) {
|
&& document.head.firstChild
|
||||||
applySections(styleId, styleHash[styleId]);
|
&& document.head.firstChild.id == 'xml-viewer-style') {
|
||||||
}
|
|
||||||
|
|
||||||
if (styleElements.size) {
|
|
||||||
// when site response is application/xml Chrome displays our style elements
|
// when site response is application/xml Chrome displays our style elements
|
||||||
// under document.documentElement as plain text so we need to move them into HEAD
|
// under document.documentElement as plain text so we need to move them into HEAD
|
||||||
// which is already autogenerated at this moment
|
// which is already autogenerated at this moment
|
||||||
if (document.head && document.head.firstChild && document.head.firstChild.id == 'xml-viewer-style') {
|
ROOT = document.head;
|
||||||
for (const id of styleElements.keys()) {
|
|
||||||
document.head.appendChild(document.getElementById(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
initObservers();
|
|
||||||
}
|
}
|
||||||
|
for (const id in styles) {
|
||||||
if (retiredStyleIds.length) {
|
applySections(id, styles[id]);
|
||||||
setTimeout(function() {
|
}
|
||||||
while (retiredStyleIds.length) {
|
initDocRewriteObserver();
|
||||||
removeStyle(retiredStyleIds.shift(), document);
|
if (retiredStyleTimers.size) {
|
||||||
|
setTimeout(() => {
|
||||||
|
for (const [id, timer] of retiredStyleTimers.entries()) {
|
||||||
|
removeStyle({id});
|
||||||
|
clearTimeout(timer);
|
||||||
}
|
}
|
||||||
}, 0);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function applySections(styleId, sections) {
|
function applySections(styleId, sections) {
|
||||||
let el = document.getElementById('stylus-' + styleId);
|
let el = document.getElementById(ID_PREFIX + styleId);
|
||||||
// Already there.
|
|
||||||
if (el) {
|
if (el) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -240,232 +215,63 @@ function applySections(styleId, sections) {
|
||||||
// This will make an HTML style element. If there's SVG embedded in an HTML document, this works on the SVG too.
|
// This will make an HTML style element. If there's SVG embedded in an HTML document, this works on the SVG too.
|
||||||
el = document.createElement('style');
|
el = document.createElement('style');
|
||||||
}
|
}
|
||||||
el.setAttribute('id', 'stylus-' + styleId);
|
Object.assign(el, {
|
||||||
el.setAttribute('class', 'stylus');
|
id: ID_PREFIX + styleId,
|
||||||
el.setAttribute('type', 'text/css');
|
className: 'stylus',
|
||||||
el.appendChild(document.createTextNode(sections.map(section => section.code).join('\n')));
|
type: 'text/css',
|
||||||
addStyleElement(el, document);
|
textContent: sections.map(section => section.code).join('\n'),
|
||||||
|
});
|
||||||
|
addStyleElement(el);
|
||||||
styleElements.set(el.id, el);
|
styleElements.set(el.id, el);
|
||||||
disabledElements.delete(styleId);
|
disabledElements.delete(styleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function addStyleElement(el, doc) {
|
function addStyleElement(el) {
|
||||||
if (!doc.documentElement || doc.getElementById(el.id)) {
|
if (ROOT && !document.getElementById(el.id)) {
|
||||||
|
ROOT.appendChild(el);
|
||||||
|
el.disabled = disableAll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function replaceAll(newStyles) {
|
||||||
|
const oldStyles = Array.prototype.slice.call(
|
||||||
|
document.querySelectorAll(`STYLE.stylus[id^="${ID_PREFIX}"]`));
|
||||||
|
oldStyles.forEach(el => (el.id += '-ghost'));
|
||||||
|
styleElements.clear();
|
||||||
|
disabledElements.clear();
|
||||||
|
retiredStyleTimers.clear();
|
||||||
|
applyStyles(newStyles);
|
||||||
|
oldStyles.forEach(el => el.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function initDocRewriteObserver() {
|
||||||
|
if (isOwnPage || docRewriteObserver || !styleElements.size) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
doc.documentElement.appendChild(doc.importNode(el, true))
|
|
||||||
.disabled = disableAll;
|
|
||||||
for (const iframe of getDynamicIFrames(doc)) {
|
|
||||||
if (iframeIsLoadingSrcDoc(iframe)) {
|
|
||||||
addStyleToIFrameSrcDoc(iframe, el);
|
|
||||||
} else {
|
|
||||||
addStyleElement(el, iframe.contentDocument);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function addDocumentStylesToIFrame(iframe) {
|
|
||||||
const doc = iframe.contentDocument;
|
|
||||||
const srcDocIsLoading = iframeIsLoadingSrcDoc(iframe);
|
|
||||||
for (const el of styleElements.values()) {
|
|
||||||
if (srcDocIsLoading) {
|
|
||||||
addStyleToIFrameSrcDoc(iframe, el);
|
|
||||||
} else {
|
|
||||||
addStyleElement(el, doc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
initObservers(doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function addDocumentStylesToAllIFrames(doc = document) {
|
|
||||||
getDynamicIFrames(doc).forEach(addDocumentStylesToIFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Only dynamic iframes get the parent document's styles. Other ones should get styles based on their own URLs.
|
|
||||||
function getDynamicIFrames(doc) {
|
|
||||||
return Array.prototype.filter.call(doc.getElementsByTagName('iframe'), iframeIsDynamic);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function iframeIsDynamic(f) {
|
|
||||||
let href;
|
|
||||||
if (f.src && f.src.startsWith('http') && new URL(f.src).origin != location.origin) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
href = f.contentDocument.location.href;
|
|
||||||
} catch (ex) {
|
|
||||||
// Cross-origin, so it's not a dynamic iframe
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return href == document.location.href || href.startsWith('about:');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function processDynamicIFrames(doc, fn, ...args) {
|
|
||||||
var iframes = doc.getElementsByTagName('iframe');
|
|
||||||
for (var i = 0, il = iframes.length; i < il; i++) {
|
|
||||||
var iframe = iframes[i];
|
|
||||||
if (iframeIsDynamic(iframe)) {
|
|
||||||
fn(...args, iframe.contentDocument);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function iframeIsLoadingSrcDoc(f) {
|
|
||||||
return f.srcdoc && f.contentDocument.all.length <= 3;
|
|
||||||
// 3 nodes or less in total (html, head, body) == new empty iframe about to be overwritten by its 'srcdoc'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function addStyleToIFrameSrcDoc(iframe, el) {
|
|
||||||
if (disableAll) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
iframe.srcdoc += el.outerHTML;
|
|
||||||
// make sure the style is added in case srcdoc was malformed
|
|
||||||
setTimeout(addStyleElement, 100, el, iframe.contentDocument);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function replaceAll(newStyles, doc) {
|
|
||||||
Array.prototype.forEach.call(doc.querySelectorAll('STYLE.stylus[id^="stylus-"]'),
|
|
||||||
e => (e.id += '-ghost'));
|
|
||||||
processDynamicIFrames(doc, replaceAll, newStyles);
|
|
||||||
if (doc == document) {
|
|
||||||
styleElements.clear();
|
|
||||||
disabledElements.clear();
|
|
||||||
applyStyles(newStyles);
|
|
||||||
replaceAllpass2(newStyles, doc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function replaceAllpass2(newStyles, doc) {
|
|
||||||
const oldStyles = doc.querySelectorAll('STYLE.stylus[id$="-ghost"]');
|
|
||||||
processDynamicIFrames(doc, replaceAllpass2, newStyles);
|
|
||||||
Array.prototype.forEach.call(oldStyles,
|
|
||||||
e => e.remove());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function onDOMContentLoaded({target = document} = {}) {
|
|
||||||
addDocumentStylesToAllIFrames(target);
|
|
||||||
if (target.iframeObserver) {
|
|
||||||
target.iframeObserver.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function initObservers(doc = document) {
|
|
||||||
if (isOwnPage || doc.rewriteObserver) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
initIFrameObserver(doc);
|
|
||||||
initDocRewriteObserver(doc);
|
|
||||||
if (doc.readyState != 'loading') {
|
|
||||||
onDOMContentLoaded({target: doc});
|
|
||||||
} else {
|
|
||||||
doc.addEventListener('DOMContentLoaded', onDOMContentLoaded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function initIFrameObserver(doc = document) {
|
|
||||||
if (!initIFrameObserver.methods) {
|
|
||||||
initIFrameObserver.methods = {
|
|
||||||
start() {
|
|
||||||
this.observe(this.doc, {childList: true, subtree: true});
|
|
||||||
},
|
|
||||||
stop() {
|
|
||||||
this.disconnect();
|
|
||||||
getDynamicIFrames(this.doc).forEach(iframe => {
|
|
||||||
const observer = iframe.contentDocument.iframeObserver;
|
|
||||||
if (observer) {
|
|
||||||
observer.stop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
doc.iframeObserver = Object.assign(
|
|
||||||
new MutationObserver(iframeObserver),
|
|
||||||
initIFrameObserver.methods, {
|
|
||||||
iframes: doc.getElementsByTagName('iframe'),
|
|
||||||
doc,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function iframeObserver(mutations, observer) {
|
|
||||||
// autoupdated HTMLCollection is superfast
|
|
||||||
if (!observer.iframes[0]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// use a much faster method for very complex pages with lots of mutations
|
|
||||||
// (observer usually receives 1k-10k mutations per call)
|
|
||||||
if (mutations.length > 1000) {
|
|
||||||
addDocumentStylesToAllIFrames(observer.doc);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (var m = 0, ml = mutations.length; m < ml; m++) {
|
|
||||||
var added = mutations[m].addedNodes;
|
|
||||||
for (var n = 0, nl = added.length; n < nl; n++) {
|
|
||||||
var node = added[n];
|
|
||||||
// process only ELEMENT_NODE
|
|
||||||
if (node.nodeType != 1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var iframes = node.localName === 'iframe' ? [node] :
|
|
||||||
node.children.length && node.getElementsByTagName('iframe');
|
|
||||||
if (iframes.length) {
|
|
||||||
// move the check out of current execution context
|
|
||||||
// because some same-domain (!) iframes fail to load when their 'contentDocument' is accessed (!)
|
|
||||||
// namely gmail's old chat iframe talkgadget.google.com
|
|
||||||
setTimeout(testIFrames, 0, iframes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function testIFrames(iframes) {
|
|
||||||
for (const iframe of iframes) {
|
|
||||||
if (iframeIsDynamic(iframe)) {
|
|
||||||
addDocumentStylesToIFrame(iframe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function initDocRewriteObserver(doc = document) {
|
|
||||||
// re-add styles if we detect documentElement being recreated
|
// re-add styles if we detect documentElement being recreated
|
||||||
doc.rewriteObserver = new MutationObserver(docRewriteObserver);
|
const reinjectStyles = () => {
|
||||||
doc.rewriteObserver.observe(doc, {childList: true});
|
ROOT = document.documentElement;
|
||||||
}
|
for (const el of styleElements.values()) {
|
||||||
|
addStyleElement(document.importNode(el, true));
|
||||||
|
}
|
||||||
function docRewriteObserver(mutations) {
|
};
|
||||||
for (const mutation of mutations) {
|
// detect documentElement being rewritten from inside the script
|
||||||
for (const node of mutation.addedNodes) {
|
docRewriteObserver = new MutationObserver(mutations => {
|
||||||
if (node.localName != 'html') {
|
for (const mutation of mutations) {
|
||||||
continue;
|
for (const node of mutation.addedNodes) {
|
||||||
}
|
if (node.localName == 'html') {
|
||||||
const doc = node.ownerDocument;
|
reinjectStyles();
|
||||||
for (const [id, el] of styleElements.entries()) {
|
return;
|
||||||
if (!doc.getElementById(id)) {
|
|
||||||
doc.documentElement.appendChild(el);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initObservers(doc);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
docRewriteObserver.observe(document, {childList: true});
|
||||||
|
// detect dynamic iframes rewritten after creation by the embedder i.e. externally
|
||||||
|
setTimeout(() => document.documentElement != ROOT && reinjectStyles());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -473,55 +279,35 @@ function orphanCheck() {
|
||||||
const port = chrome.runtime.connect();
|
const port = chrome.runtime.connect();
|
||||||
if (port) {
|
if (port) {
|
||||||
port.disconnect();
|
port.disconnect();
|
||||||
//console.debug('orphanCheck: still connected');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//console.debug('orphanCheck: disconnected');
|
|
||||||
|
|
||||||
// we're orphaned due to an extension update
|
// we're orphaned due to an extension update
|
||||||
// we can detach the mutation observer
|
// we can detach the mutation observer
|
||||||
|
if (docRewriteObserver) {
|
||||||
|
docRewriteObserver.disconnect();
|
||||||
|
}
|
||||||
// we can detach event listeners
|
// we can detach event listeners
|
||||||
(function unbind(doc) {
|
|
||||||
if (doc.iframeObserver) {
|
|
||||||
doc.iframeObserver.disconnect();
|
|
||||||
delete doc.iframeObserver;
|
|
||||||
}
|
|
||||||
if (doc.rewriteObserver) {
|
|
||||||
doc.rewriteObserver.disconnect();
|
|
||||||
delete doc.rewriteObserver;
|
|
||||||
}
|
|
||||||
doc.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
|
|
||||||
getDynamicIFrames(doc).forEach(iframe => unbind(iframe.contentDocument));
|
|
||||||
})(document);
|
|
||||||
window.removeEventListener(chrome.runtime.id, orphanCheck, true);
|
window.removeEventListener(chrome.runtime.id, orphanCheck, true);
|
||||||
// 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 our globals in this context to free up memory
|
||||||
[ // functions
|
[ // functions
|
||||||
'addDocumentStylesToAllIFrames',
|
|
||||||
'addDocumentStylesToIFrame',
|
|
||||||
'addStyleElement',
|
'addStyleElement',
|
||||||
'addStyleToIFrameSrcDoc',
|
|
||||||
'applyOnMessage',
|
'applyOnMessage',
|
||||||
'applySections',
|
'applySections',
|
||||||
'applyStyles',
|
'applyStyles',
|
||||||
|
'applyStyleState',
|
||||||
'doDisableAll',
|
'doDisableAll',
|
||||||
'getDynamicIFrames',
|
|
||||||
'iframeIsDynamic',
|
|
||||||
'iframeIsLoadingSrcDoc',
|
|
||||||
'initDocRewriteObserver',
|
'initDocRewriteObserver',
|
||||||
'initIFrameObserver',
|
|
||||||
'orphanCheck',
|
'orphanCheck',
|
||||||
'processDynamicIFrames',
|
|
||||||
'removeStyle',
|
'removeStyle',
|
||||||
'replaceAll',
|
'replaceAll',
|
||||||
'replaceAllpass2',
|
|
||||||
'requestStyles',
|
'requestStyles',
|
||||||
'retireStyle',
|
|
||||||
'styleObserver',
|
|
||||||
// variables
|
// variables
|
||||||
'docRewriteObserver',
|
'ROOT',
|
||||||
'iframeObserver',
|
'disabledElements',
|
||||||
'retiredStyleIds',
|
'retiredStyleTimers',
|
||||||
'styleElements',
|
'styleElements',
|
||||||
|
'docRewriteObserver',
|
||||||
].forEach(fn => (window[fn] = null));
|
].forEach(fn => (window[fn] = null));
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,18 +208,19 @@ function injectContentScripts() {
|
||||||
m == '<all_urls>' ? m : wildcardAsRegExp(m)
|
m == '<all_urls>' ? m : wildcardAsRegExp(m)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
// also inject in chrome://newtab/ page
|
chrome.tabs.query({}, tabs => {
|
||||||
chrome.tabs.query({url: '*://*/*'}, tabs => {
|
|
||||||
for (const tab of tabs) {
|
for (const tab of tabs) {
|
||||||
for (const cs of contentScripts) {
|
for (const cs of contentScripts) {
|
||||||
for (const m of cs.matches) {
|
for (const m of cs.matches) {
|
||||||
if (m == '<all_urls>' || tab.url.match(m)) {
|
if ((m == '<all_urls>' || tab.url.match(m))
|
||||||
|
&& (!tab.url.startsWith('chrome') || tab.url == 'chrome://newtab/')) {
|
||||||
chrome.tabs.sendMessage(tab.id, {method: 'ping'}, pong => {
|
chrome.tabs.sendMessage(tab.id, {method: 'ping'}, pong => {
|
||||||
if (!pong) {
|
if (!pong) {
|
||||||
chrome.tabs.executeScript(tab.id, {
|
chrome.tabs.executeScript(tab.id, {
|
||||||
file: cs.js[0],
|
file: cs.js[0],
|
||||||
runAt: cs.run_at,
|
runAt: cs.run_at,
|
||||||
allFrames: cs.all_frames,
|
allFrames: cs.all_frames,
|
||||||
|
matchAboutBlank: cs.match_about_blank,
|
||||||
}, ignoreChromeError);
|
}, ignoreChromeError);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
"matches": ["<all_urls>"],
|
"matches": ["<all_urls>"],
|
||||||
"run_at": "document_start",
|
"run_at": "document_start",
|
||||||
"all_frames": true,
|
"all_frames": true,
|
||||||
|
"match_about_blank": true,
|
||||||
"js": ["apply.js"]
|
"js": ["apply.js"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue
Block a user