diff --git a/background/background.js b/background/background.js
index 454239fa..87ad49eb 100644
--- a/background/background.js
+++ b/background/background.js
@@ -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
diff --git a/background/storage.js b/background/storage.js
index bc566874..d4554e75 100644
--- a/background/storage.js
+++ b/background/storage.js
@@ -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));
+ }
+}
diff --git a/content/apply.js b/content/apply.js
index f29b6e4a..c93ebc7d 100644
--- a/content/apply.js
+++ b/content/apply.js
@@ -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;
}
diff --git a/manage.html b/manage.html
index 6548ed03..bd6f73d8 100644
--- a/manage.html
+++ b/manage.html
@@ -7,12 +7,6 @@
-