Merge pull request #107 from tophf/iframe-observer-speedup
Make iframe observer 10-100 times faster
This commit is contained in:
commit
a7cb548f92
164
apply.js
164
apply.js
|
@ -1,18 +1,23 @@
|
||||||
requestStyles();
|
var g_disableAll = false;
|
||||||
|
var g_styleElements = {};
|
||||||
function requestStyles() {
|
var iframeObserver;
|
||||||
// If this is a Stylish page (Edit Style or Manage Styles),
|
|
||||||
// we'll request the styles directly to minimize delay and flicker,
|
initObserver();
|
||||||
// unless Chrome still starts up and the background page isn't fully loaded.
|
requestStyles();
|
||||||
// (Note: in this case the function may be invoked again from applyStyles.)
|
|
||||||
var request = {method: "getStyles", matchUrl: location.href, enabled: true, asHash: true};
|
function requestStyles() {
|
||||||
if (location.href.indexOf(chrome.extension.getURL("")) == 0) {
|
// If this is a Stylish page (Edit Style or Manage Styles),
|
||||||
var bg = chrome.extension.getBackgroundPage();
|
// we'll request the styles directly to minimize delay and flicker,
|
||||||
if (bg && bg.getStyles) {
|
// unless Chrome still starts up and the background page isn't fully loaded.
|
||||||
bg.getStyles(request, applyStyles);
|
// (Note: in this case the function may be invoked again from applyStyles.)
|
||||||
return;
|
var request = {method: "getStyles", matchUrl: location.href, enabled: true, asHash: true};
|
||||||
}
|
if (location.href.indexOf(chrome.extension.getURL("")) == 0) {
|
||||||
}
|
var bg = chrome.extension.getBackgroundPage();
|
||||||
|
if (bg && bg.getStyles) {
|
||||||
|
bg.getStyles(request, applyStyles);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
chrome.extension.sendMessage(request, applyStyles);
|
chrome.extension.sendMessage(request, applyStyles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,21 +41,30 @@ chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
|
||||||
case "styleReplaceAll":
|
case "styleReplaceAll":
|
||||||
replaceAll(request.styles, document);
|
replaceAll(request.styles, document);
|
||||||
break;
|
break;
|
||||||
case "realURL":
|
case "realURL":
|
||||||
sendResponse(location.href);
|
sendResponse(location.href);
|
||||||
break;
|
break;
|
||||||
case "styleDisableAll":
|
case "styleDisableAll":
|
||||||
disableAll(request.disableAll);
|
disableAll(request.disableAll);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var g_disableAll = false;
|
|
||||||
function disableAll(disable) {
|
function disableAll(disable) {
|
||||||
if (!disable === !g_disableAll) return;
|
if (!disable === !g_disableAll) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
g_disableAll = disable;
|
g_disableAll = disable;
|
||||||
|
if (g_disableAll) {
|
||||||
|
iframeObserver.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
disableSheets(g_disableAll, document);
|
disableSheets(g_disableAll, document);
|
||||||
|
|
||||||
|
if (!g_disableAll && document.readyState != "loading") {
|
||||||
|
iframeObserver.start();
|
||||||
|
}
|
||||||
|
|
||||||
function disableSheets(disable, doc) {
|
function disableSheets(disable, doc) {
|
||||||
Array.prototype.forEach.call(doc.styleSheets, function(stylesheet) {
|
Array.prototype.forEach.call(doc.styleSheets, function(stylesheet) {
|
||||||
if (stylesheet.ownerNode.classList.contains("stylish")) {
|
if (stylesheet.ownerNode.classList.contains("stylish")) {
|
||||||
|
@ -58,6 +72,10 @@ function disableAll(disable) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
getDynamicIFrames(doc).forEach(function(iframe) {
|
getDynamicIFrames(doc).forEach(function(iframe) {
|
||||||
|
if (!disable) {
|
||||||
|
// update the IFRAME if it was created while the observer was disconnected
|
||||||
|
addDocumentStylesToIFrame(iframe);
|
||||||
|
}
|
||||||
disableSheets(disable, iframe.contentDocument);
|
disableSheets(disable, iframe.contentDocument);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -65,8 +83,12 @@ function disableAll(disable) {
|
||||||
|
|
||||||
function removeStyle(id, doc) {
|
function removeStyle(id, doc) {
|
||||||
var e = doc.getElementById("stylish-" + id);
|
var e = doc.getElementById("stylish-" + id);
|
||||||
|
delete g_styleElements["stylish-" + id];
|
||||||
if (e) {
|
if (e) {
|
||||||
e.parentNode.removeChild(e);
|
e.remove();
|
||||||
|
}
|
||||||
|
if (doc == document && Object.keys(g_styleElements).length == 0) {
|
||||||
|
iframeObserver.disconnect();
|
||||||
}
|
}
|
||||||
getDynamicIFrames(doc).forEach(function(iframe) {
|
getDynamicIFrames(doc).forEach(function(iframe) {
|
||||||
removeStyle(id, iframe.contentDocument);
|
removeStyle(id, iframe.contentDocument);
|
||||||
|
@ -74,10 +96,10 @@ function removeStyle(id, doc) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyStyles(styleHash) {
|
function applyStyles(styleHash) {
|
||||||
if (!styleHash) { // Chrome is starting up
|
if (!styleHash) { // Chrome is starting up
|
||||||
requestStyles();
|
requestStyles();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ("disableAll" in styleHash) {
|
if ("disableAll" in styleHash) {
|
||||||
disableAll(styleHash.disableAll);
|
disableAll(styleHash.disableAll);
|
||||||
delete styleHash.disableAll;
|
delete styleHash.disableAll;
|
||||||
|
@ -86,6 +108,13 @@ function applyStyles(styleHash) {
|
||||||
for (var styleId in styleHash) {
|
for (var styleId in styleHash) {
|
||||||
applySections(styleId, styleHash[styleId]);
|
applySections(styleId, styleHash[styleId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.keys(g_styleElements).length) {
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
getDynamicIFrames(document).forEach(addDocumentStylesToIFrame);
|
||||||
|
});
|
||||||
|
iframeObserver.start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applySections(styleId, sections) {
|
function applySections(styleId, sections) {
|
||||||
|
@ -108,16 +137,36 @@ function applySections(styleId, sections) {
|
||||||
return section.code;
|
return section.code;
|
||||||
}).join("\n")));
|
}).join("\n")));
|
||||||
addStyleElement(styleElement, document);
|
addStyleElement(styleElement, document);
|
||||||
|
g_styleElements[styleElement.id] = styleElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addStyleElement(styleElement, doc) {
|
function addStyleElement(styleElement, doc) {
|
||||||
|
if (!doc.documentElement || doc.getElementById(styleElement.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
doc.documentElement.appendChild(doc.importNode(styleElement, true))
|
doc.documentElement.appendChild(doc.importNode(styleElement, true))
|
||||||
.disabled = g_disableAll;
|
.disabled = g_disableAll;
|
||||||
getDynamicIFrames(doc).forEach(function(iframe) {
|
getDynamicIFrames(doc).forEach(function(iframe) {
|
||||||
addStyleElement(styleElement, iframe.contentDocument);
|
if (iframeIsLoadingSrcDoc(iframe)) {
|
||||||
|
addStyleToIFrameSrcDoc(iframe, styleElement);
|
||||||
|
} else {
|
||||||
|
addStyleElement(styleElement, iframe.contentDocument);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addDocumentStylesToIFrame(iframe) {
|
||||||
|
var doc = iframe.contentDocument;
|
||||||
|
var srcDocIsLoading = iframeIsLoadingSrcDoc(iframe);
|
||||||
|
for (var id in g_styleElements) {
|
||||||
|
if (srcDocIsLoading) {
|
||||||
|
addStyleToIFrameSrcDoc(iframe, g_styleElements[id]);
|
||||||
|
} else {
|
||||||
|
addStyleElement(g_styleElements[id], doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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 Array.prototype.filter.call(doc.getElementsByTagName('iframe'), iframeIsDynamic);
|
return Array.prototype.filter.call(doc.getElementsByTagName('iframe'), iframeIsDynamic);
|
||||||
|
@ -134,32 +183,57 @@ function iframeIsDynamic(f) {
|
||||||
return href == document.location.href || href.indexOf("about:") == 0;
|
return href == document.location.href || href.indexOf("about:") == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, styleElement) {
|
||||||
|
if (g_disableAll) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
iframe.srcdoc += styleElement.outerHTML;
|
||||||
|
// make sure the style is added in case srcdoc was malformed
|
||||||
|
setTimeout(addStyleElement.bind(null, styleElement, iframe.contentDocument), 100);
|
||||||
|
}
|
||||||
|
|
||||||
function replaceAll(newStyles, doc) {
|
function replaceAll(newStyles, doc) {
|
||||||
Array.prototype.forEach.call(doc.querySelectorAll("STYLE.stylish"), function(style) {
|
Array.prototype.forEach.call(doc.querySelectorAll("STYLE.stylish"), function(style) {
|
||||||
style.parentNode.removeChild(style);
|
style.remove();
|
||||||
});
|
});
|
||||||
applyStyles(newStyles);
|
if (doc == document) {
|
||||||
|
g_styleElements = {};
|
||||||
|
applyStyles(newStyles);
|
||||||
|
}
|
||||||
getDynamicIFrames(doc).forEach(function(iframe) {
|
getDynamicIFrames(doc).forEach(function(iframe) {
|
||||||
replaceAll(newStyles, iframe.contentDocument);
|
replaceAll(newStyles, iframe.contentDocument);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Observe dynamic IFRAMEs being added
|
// Observe dynamic IFRAMEs being added
|
||||||
var iframeObserver = new MutationObserver(function(mutations) {
|
function initObserver() {
|
||||||
var styles = Array.prototype.slice.call(document.querySelectorAll('STYLE.stylish'));
|
iframeObserver = new MutationObserver(function(mutations) {
|
||||||
if (styles.length == 0) {
|
if (mutations.length > 1000) {
|
||||||
return;
|
// use a much faster method for very complex pages with 100,000 mutations
|
||||||
}
|
// (observer usually receives 1k-10k mutations per call)
|
||||||
mutations.filter(function(mutation) {
|
getDynamicIFrames(document).forEach(addDocumentStylesToIFrame);
|
||||||
return "childList" === mutation.type;
|
return;
|
||||||
}).forEach(function(mutation) {
|
}
|
||||||
Array.prototype.filter.call(mutation.addedNodes, function(node) { return "IFRAME" === node.tagName; }).filter(iframeIsDynamic).forEach(function(iframe) {
|
for (var m = 0, ml = mutations.length; m < ml; m++) {
|
||||||
var doc = iframe.contentDocument;
|
var mutation = mutations[m];
|
||||||
styles.forEach(function(style) {
|
if (mutation.type === "childList") {
|
||||||
doc.documentElement.appendChild(doc.importNode(style, true))
|
for (var n = 0, nodes = mutation.addedNodes, nl = nodes.length; n < nl; n++) {
|
||||||
.disabled = g_disableAll;
|
var node = nodes[n];
|
||||||
});
|
if (node.localName === "iframe" && iframeIsDynamic(node)) {
|
||||||
});
|
addDocumentStylesToIFrame(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
iframeObserver.observe(document, {childList: true, subtree: true});
|
iframeObserver.start = function() {
|
||||||
|
// will be ignored by browser if already observing
|
||||||
|
iframeObserver.observe(document, {childList: true, subtree: true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user