inject styles only in visible frames

fixes #1033
This commit is contained in:
tophf 2021-02-28 22:32:25 +03:00
parent a56e528b31
commit c0eace302f
2 changed files with 40 additions and 8 deletions

View File

@ -42,8 +42,9 @@ const navMan = (() => {
/** @this {string} type */ /** @this {string} type */
function onFakeNavigation(data) { function onFakeNavigation(data) {
const {url, frameId} = data;
onNavigation.call(this, data); onNavigation.call(this, data);
msg.sendTab(data.tabId, {method: 'urlChanged'}, {frameId: data.frameId}) msg.sendTab(data.tabId, {method: 'urlChanged', url}, {frameId})
.catch(msg.ignoreError); .catch(msg.ignoreError);
} }
})(); })();

View File

@ -20,8 +20,9 @@
compare: (a, b) => a.id - b.id, compare: (a, b) => a.id - b.id,
onUpdate: onInjectorUpdate, onUpdate: onInjectorUpdate,
}); });
// dynamic about: and javascript: iframes don't have a URL yet so we'll use their parent // dynamic iframes don't have a URL yet so we'll use their parent's URL (hash isn't inherited)
const matchUrl = isFrameAboutBlank && tryCatch(() => parent.location.href) || location.href; let matchUrl = isFrameAboutBlank && tryCatch(() => parent.location.href.split('#')[0]) ||
location.href;
// save it now because chrome.runtime will be unavailable in the orphaned script // save it now because chrome.runtime will be unavailable in the orphaned script
const orphanEventId = chrome.runtime.id; const orphanEventId = chrome.runtime.id;
@ -34,6 +35,20 @@
let lazyBadge = isFrame; let lazyBadge = isFrame;
let parentDomain; let parentDomain;
/* about:blank iframes are often used by sites for file upload or background tasks
* and they may break if unexpected DOM stuff is present at `load` event
* so we'll add the styles only if the iframe becomes visible */
const {IntersectionObserver} = window;
/** @type IntersectionObserver */
let xo;
if (IntersectionObserver) {
window[Symbol.for('xo')] = (el, cb) => {
if (!xo) xo = new IntersectionObserver(onIntersect, {rootMargin: '100%'});
el.cb = cb;
xo.observe(el);
};
}
// Declare all vars before init() or it'll throw due to "temporal dead zone" of const/let // Declare all vars before init() or it'll throw due to "temporal dead zone" of const/let
const ready = init(); const ready = init();
@ -74,10 +89,7 @@
tryCatch(() => parent[parent.Symbol.for(SYM_ID)]); tryCatch(() => parent[parent.Symbol.for(SYM_ID)]);
const styles = const styles =
window[SYM] || window[SYM] ||
/* about:blank iframes are often used by sites for file upload or background tasks parentStyles && await new Promise(onFrameElementInView) && parentStyles ||
* and they may break if unexpected DOM stuff is present at `load` event
* so we'll add the styles in the next tick */
parentStyles && await new Promise(requestAnimationFrame) && parentStyles ||
!isFrameAboutBlank && chrome.app && !chrome.tabs && tryCatch(getStylesViaXhr) || !isFrameAboutBlank && chrome.app && !chrome.tabs && tryCatch(getStylesViaXhr) ||
await API.styles.getSectionsByUrl(matchUrl, null, true); await API.styles.getSectionsByUrl(matchUrl, null, true);
isDisabled = styles.disableAll; isDisabled = styles.disableAll;
@ -147,7 +159,8 @@
break; break;
case 'urlChanged': case 'urlChanged':
if (!hasStyles && isDisabled) break; if (!hasStyles && isDisabled || matchUrl === request.url) break;
matchUrl = request.url;
API.styles.getSectionsByUrl(matchUrl).then(sections => { API.styles.getSectionsByUrl(matchUrl).then(sections => {
hasStyles = true; hasStyles = true;
styleInjector.replace(sections); styleInjector.replace(sections);
@ -209,6 +222,24 @@
).catch(msg.ignoreError); ).catch(msg.ignoreError);
} }
function onFrameElementInView(cb) {
if (IntersectionObserver) {
parent[parent.Symbol.for('xo')](frameElement, cb);
} else {
requestAnimationFrame(cb);
}
}
/** @param {IntersectionObserverEntry[]} entries */
function onIntersect(entries) {
for (const e of entries) {
if (e.isIntersecting) {
xo.unobserve(e.target);
tryCatch(e.target.cb);
}
}
}
function tryCatch(func, ...args) { function tryCatch(func, ...args) {
try { try {
return func(...args); return func(...args);