globally disable CSS transitions for a moment during page opening
the problem we fix is that since we add the styles asynchronously, the browsers, esp. Firefox, sometimes apply transitions from the null/default state to the one specified in the injected CSS. supersedes72e8213b
and4dbca46b
This commit is contained in:
parent
0c205df108
commit
519d745f59
|
@ -1,4 +1,5 @@
|
|||
/* global dbExec, getStyles, saveStyle */
|
||||
/* global handleCssTransitionBug */
|
||||
'use strict';
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
|
@ -211,7 +212,10 @@ contextMenus = Object.assign({
|
|||
|
||||
function webNavigationListener(method, {url, tabId, frameId}) {
|
||||
getStyles({matchUrl: url, enabled: true, asHash: true}).then(styles => {
|
||||
if (method && !url.startsWith('chrome:') && tabId >= 0) {
|
||||
if (method && URLS.supported(url) && tabId >= 0) {
|
||||
if (method === 'styleApply') {
|
||||
handleCssTransitionBug(tabId, frameId, styles);
|
||||
}
|
||||
chrome.tabs.sendMessage(tabId, {
|
||||
method,
|
||||
// ping own page so it retrieves the styles directly
|
||||
|
|
|
@ -8,6 +8,11 @@ const RX_CSS_COMMENTS = /\/\*[\s\S]*?\*\//g;
|
|||
// eslint-disable-next-line no-var
|
||||
var SLOPPY_REGEXP_PREFIX = '\0';
|
||||
|
||||
// CSS transition bug workaround: since we insert styles asynchronously,
|
||||
// the browsers, especially Firefox, may apply all transitions on page load
|
||||
const CSS_TRANSITION_SUPPRESSOR = '* { transition: none !important; }';
|
||||
const RX_CSS_TRANSITION_DETECTOR = /([\s\n;/{]|-webkit-|-moz-)transition[\s\n]*:[\s\n]*(?!none)/;
|
||||
|
||||
// Note, only 'var'-declared variables are visible from another extension page
|
||||
// eslint-disable-next-line no-var
|
||||
var cachedStyles = {
|
||||
|
@ -16,6 +21,7 @@ var cachedStyles = {
|
|||
filters: new Map(), // filterStyles() parameters mapped to the returned results, 10k max
|
||||
regexps: new Map(), // compiled style regexps
|
||||
urlDomains: new Map(), // getDomain() results for 100 last checked urls
|
||||
needTransitionPatch: new Map(), // FF bug workaround
|
||||
mutex: {
|
||||
inProgress: false, // while getStyles() is reading IndexedDB all subsequent calls
|
||||
onDone: [], // to getStyles() are queued and resolved when the first one finishes
|
||||
|
@ -517,6 +523,7 @@ function invalidateCache({added, updated, deletedId} = {}) {
|
|||
if (cached) {
|
||||
Object.assign(cached, updated);
|
||||
cachedStyles.filters.clear();
|
||||
cachedStyles.needTransitionPatch.delete(id);
|
||||
return;
|
||||
} else {
|
||||
added = updated;
|
||||
|
@ -527,6 +534,7 @@ function invalidateCache({added, updated, deletedId} = {}) {
|
|||
cachedStyles.list.push(added);
|
||||
cachedStyles.byId.set(added.id, added);
|
||||
cachedStyles.filters.clear();
|
||||
cachedStyles.needTransitionPatch.delete(id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -536,11 +544,13 @@ function invalidateCache({added, updated, deletedId} = {}) {
|
|||
cachedStyles.list.splice(cachedIndex, 1);
|
||||
cachedStyles.byId.delete(deletedId);
|
||||
cachedStyles.filters.clear();
|
||||
cachedStyles.needTransitionPatch.delete(id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
cachedStyles.list = null;
|
||||
cachedStyles.filters.clear();
|
||||
cachedStyles.needTransitionPatch.clear(id);
|
||||
}
|
||||
|
||||
|
||||
|
@ -612,3 +622,78 @@ function calcStyleDigest(style) {
|
|||
return parts.join('');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleCssTransitionBug(tabId, frameId, styles) {
|
||||
for (let id in styles) {
|
||||
id |= 0;
|
||||
if (!id) {
|
||||
continue;
|
||||
}
|
||||
let need = cachedStyles.needTransitionPatch.get(id);
|
||||
if (need === false) {
|
||||
continue;
|
||||
}
|
||||
if (need !== true) {
|
||||
need = styles[id].some(sectionContainsTransitions);
|
||||
cachedStyles.needTransitionPatch.set(id, need);
|
||||
if (!need) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (FIREFOX) {
|
||||
patchFirefox();
|
||||
} else {
|
||||
styles.needTransitionPatch = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
function patchFirefox() {
|
||||
browser.tabs.insertCSS(tabId, {
|
||||
frameId,
|
||||
code: CSS_TRANSITION_SUPPRESSOR,
|
||||
cssOrigin: 'user',
|
||||
runAt: 'document_start',
|
||||
matchAboutBlank: true,
|
||||
}).then(() => setTimeout(() => {
|
||||
browser.tabs.removeCSS(tabId, {
|
||||
frameId,
|
||||
code: CSS_TRANSITION_SUPPRESSOR,
|
||||
cssOrigin: 'user',
|
||||
matchAboutBlank: true,
|
||||
}).catch(ignoreChromeError);
|
||||
})).catch(ignoreChromeError);
|
||||
}
|
||||
|
||||
function sectionContainsTransitions(section) {
|
||||
let code = section.code;
|
||||
const firstTransition = code.indexOf('transition');
|
||||
if (firstTransition < 0) {
|
||||
return false;
|
||||
}
|
||||
const firstCmt = code.indexOf('/*');
|
||||
// check the part before the first comment
|
||||
if (firstCmt < 0 || firstTransition < firstCmt) {
|
||||
if (quickCheckAround(code, firstTransition)) {
|
||||
return true;
|
||||
} else if (firstCmt < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// check the rest
|
||||
const lastCmt = code.lastIndexOf('*/');
|
||||
if (lastCmt < firstCmt) {
|
||||
// the comment is unclosed and we already checked the preceding part
|
||||
return false;
|
||||
}
|
||||
let mid = code.slice(firstCmt, lastCmt + 2);
|
||||
mid = mid.indexOf('*/') === mid.length - 2 ? '' : mid.replace(RX_CSS_COMMENTS, '');
|
||||
code = mid + code.slice(lastCmt + 2);
|
||||
return quickCheckAround(code) || RX_CSS_TRANSITION_DETECTOR.test(code);
|
||||
}
|
||||
|
||||
function quickCheckAround(code, pos = code.indexOf('transition')) {
|
||||
return RX_CSS_TRANSITION_DETECTOR.test(code.substr(Math.max(0, pos - 10), 50));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -199,8 +199,25 @@ function applyStyles(styles) {
|
|||
// which is already autogenerated at this moment
|
||||
ROOT = document.head;
|
||||
}
|
||||
if (styles.needTransitionPatch) {
|
||||
// CSS transition bug workaround: since we insert styles asynchronously,
|
||||
// the browsers, especially Firefox, may apply all transitions on page load
|
||||
delete styles.needTransitionPatch;
|
||||
const className = chrome.runtime.id + '-transition-bug-fix';
|
||||
const docId = document.documentElement.id ? '#' + document.documentElement.id : '';
|
||||
document.documentElement.classList.add(className);
|
||||
applySections(0, `
|
||||
${docId}.${className}:root * {
|
||||
transition: none !important;
|
||||
}
|
||||
`);
|
||||
setTimeout(() => {
|
||||
removeStyle({id: 0});
|
||||
document.documentElement.classList.remove(className);
|
||||
});
|
||||
}
|
||||
for (const id in styles) {
|
||||
applySections(id, styles[id]);
|
||||
applySections(id, styles[id].map(section => section.code).join('\n'));
|
||||
}
|
||||
initDocRewriteObserver();
|
||||
initDocRootObserver();
|
||||
|
@ -215,7 +232,7 @@ function applyStyles(styles) {
|
|||
}
|
||||
|
||||
|
||||
function applySections(styleId, sections) {
|
||||
function applySections(styleId, code) {
|
||||
let el = document.getElementById(ID_PREFIX + styleId);
|
||||
if (el) {
|
||||
return;
|
||||
|
@ -234,11 +251,12 @@ function applySections(styleId, sections) {
|
|||
id: ID_PREFIX + styleId,
|
||||
className: 'stylus',
|
||||
type: 'text/css',
|
||||
textContent: sections.map(section => section.code).join('\n'),
|
||||
textContent: code,
|
||||
});
|
||||
addStyleElement(el);
|
||||
styleElements.set(el.id, el);
|
||||
disabledElements.delete(Number(styleId));
|
||||
return el;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -7,12 +7,6 @@
|
|||
<link rel="stylesheet" href="msgbox/msgbox.css">
|
||||
|
||||
<style id="style-overrides"></style>
|
||||
<style id="firefox-transitions-bug-suppressor">
|
||||
/* increased specificity to override sane selectors in user styles */
|
||||
html#stylus.firefox #stylus-manage #header *:not(body) {
|
||||
transition: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Notes:
|
||||
* Chrome doesn't garbage-collect (or even leaks) SVG <symbol> referenced via <use> so we'll embed the code directly
|
||||
|
|
|
@ -76,10 +76,6 @@ onDOMready().then(onBackgroundReady).then(() => {
|
|||
});
|
||||
|
||||
filterOnChange({forceRefilter: true});
|
||||
|
||||
if (FIREFOX) {
|
||||
$('#firefox-transitions-bug-suppressor').remove();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user