Add: implement bug 461

This commit is contained in:
eight 2018-10-11 17:32:27 +08:00
parent 7ed39ab6ef
commit c657d7e55c
2 changed files with 106 additions and 76 deletions

View File

@ -146,6 +146,8 @@ prefs.subscribe([
'iconset', 'iconset',
], () => debounce(refreshAllIcons)); ], () => debounce(refreshAllIcons));
prefs.initializing.then(refreshIconBadgeColor);
// ************************************************************************* // *************************************************************************
chrome.runtime.onInstalled.addListener(({reason}) => { chrome.runtime.onInstalled.addListener(({reason}) => {
if (reason !== 'update') return; if (reason !== 'update') return;

View File

@ -1,5 +1,6 @@
/* eslint no-var: 0 */ /* eslint no-var: 0 */
/* global msg API prefs */ /* global msg API prefs */
/* exported APPLY */
'use strict'; 'use strict';
// some weird bug in new Chrome: the content script gets injected multiple times // some weird bug in new Chrome: the content script gets injected multiple times
@ -12,17 +13,9 @@ const APPLY = (() => {
var disableAll = false; var disableAll = false;
var styleElements = new Map(); var styleElements = new Map();
var disabledElements = new Map(); var disabledElements = new Map();
var retiredStyleTimers = new Map();
var docRewriteObserver; var docRewriteObserver;
var docRootObserver; var docRootObserver;
// FF59+ bug workaround
// See https://github.com/openstyles/stylus/issues/461
// Since it's easy to spoof the browser version in pre-Quantum FF we're checking
// for getPreventDefault which got removed in FF59 https://bugzil.la/691151
const FF_BUG461 = !CHROME && !isOwnPage && !Event.prototype.getPreventDefault;
const pageContextQueue = [];
// FIXME: styleViaAPI // FIXME: styleViaAPI
// FIXME: getStylesFallback? // FIXME: getStylesFallback?
if (!chrome.app && document instanceof XMLDocument) { if (!chrome.app && document instanceof XMLDocument) {
@ -33,16 +26,19 @@ const APPLY = (() => {
const styles = Object.values(result); const styles = Object.values(result);
// CSS transition bug workaround: since we insert styles asynchronously, // CSS transition bug workaround: since we insert styles asynchronously,
// the browsers, especially Firefox, may apply all transitions on page load // the browsers, especially Firefox, may apply all transitions on page load
if (styles.some(s => s.code.includes('transition'))) { applyStyles(styles, () => {
applyTransitionPatch(); if (styles.some(s => s.code.includes('transition'))) {
} applyTransitionPatch();
applyStyles(styles); }
});
}); });
} }
msg.onTab(applyOnMessage); msg.onTab(applyOnMessage);
if (!isOwnPage) { if (!isOwnPage) {
window.dispatchEvent(new CustomEvent(chrome.runtime.id)); window.dispatchEvent(new CustomEvent(chrome.runtime.id, {
detail: {method: 'orphan'}
}));
window.addEventListener(chrome.runtime.id, orphanCheck, true); window.addEventListener(chrome.runtime.id, orphanCheck, true);
} }
@ -54,6 +50,66 @@ const APPLY = (() => {
prefs.subscribe(['exposeIframes'], updateExposeIframes); prefs.subscribe(['exposeIframes'], updateExposeIframes);
} }
const setStyleContent = createSetStyleContent();
function createSetStyleContent() {
// FF59+ bug workaround
// See https://github.com/openstyles/stylus/issues/461
// Since it's easy to spoof the browser version in pre-Quantum FF we're checking
// for getPreventDefault which got removed in FF59 https://bugzil.la/691151
// const FF_BUG461 = !CHROME && !isOwnPage && !Event.prototype.getPreventDefault;
const EVENT_NAME = chrome.runtime.id;
if (CHROME || isOwnPage || Event.prototype.getPreventDefault || !injectPageScript()) {
return (el, content) => {
// FIXME: do we have to keep el.sheet.disabled?
el.textContent = content;
};
}
return (el, content) => {
window.dispatchEvent(EVENT_NAME, new CustomEvent({detail: {
method: 'setStyleContent',
id: el.id,
content
}}));
};
function injectPageScript() {
const script = document.createElement('script');
const scriptContent = EVENT_NAME => {
document.currentScript.remove();
window.dispatchEvent(new CustomEvent(EVENT_NAME, {
detail: {method: 'pageScriptOK'}
}));
window.addEventListener(EVENT_NAME, function handler(e) {
const {method, id, content} = e.detail;
if (method === 'setStyleContent') {
const el = document.getElementById(id);
if (!el) {
return;
}
const disabled = el.sheet.disabled;
el.textContent = content;
el.sheet.disabled = disabled;
} else if (method === 'orphan') {
window.removeEventListener(EVENT_NAME, handler);
}
}, true);
};
script.text = `(${scriptContent})(${JSON.stringify(EVENT_NAME)})`;
let ok = false;
const check = e => {
if (e.detail.method === 'pageScriptOK') {
ok = true;
}
};
window.addEventListener(EVENT_NAME, check, true);
ROOT.appendChild(script);
window.removeEventListener(EVENT_NAME, check, true);
script.remove();
return ok;
}
}
function getMatchUrl() { function getMatchUrl() {
var matchUrl = location.href; var matchUrl = location.href;
if (!matchUrl.match(/^(http|file|chrome|ftp)/)) { if (!matchUrl.match(/^(http|file|chrome|ftp)/)) {
@ -222,30 +278,20 @@ const APPLY = (() => {
updateCount(); updateCount();
} }
function removeStyle({id, retire = false}) { function removeStyle({id}) {
const el = document.getElementById(ID_PREFIX + id); const el = document.getElementById(ID_PREFIX + id);
if (el) { if (el) {
if (retire) { docRootObserver.evade(() => el.remove());
// to avoid page flicker when the style is updated
// instead of removing it immediately we rename its ID and queue it
// to be deleted in applyStyles after a new version is fetched and applied
const deadID = id + '-ghost';
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 {
docRootObserver.evade(() => el.remove());
}
} }
disabledElements.delete(id); disabledElements.delete(id);
retiredStyleTimers.delete(id); if (styleElements.delete(id)) {
if (styleElements.delete(ID_PREFIX + id)) {
updateCount(); updateCount();
} }
} }
function applyStyles(styles) { function applyStyles(styles, done) {
if (!styles.length) { if (!styles.length) {
done();
return; return;
} }
@ -253,23 +299,21 @@ const APPLY = (() => {
new MutationObserver((mutations, observer) => { new MutationObserver((mutations, observer) => {
if (document.documentElement) { if (document.documentElement) {
observer.disconnect(); observer.disconnect();
applyStyles(styles); applyStyles(styles, done);
} }
}).observe(document, {childList: true}); }).observe(document, {childList: true});
return; return;
} }
if (styles.length) { if (docRootObserver) {
if (docRootObserver) { docRootObserver.stop();
docRootObserver.stop(); } else {
} else { initDocRootObserver();
initDocRootObserver();
}
for (const section of styles) {
applySections(section.id, section.code);
}
docRootObserver.firstStart();
} }
for (const section of styles) {
applySections(section.id, section.code);
}
docRootObserver.firstStart();
// FIXME // FIXME
// if (FF_BUG461 && (gotNewStyles || styles.needTransitionPatch)) { // if (FF_BUG461 && (gotNewStyles || styles.needTransitionPatch)) {
@ -280,34 +324,17 @@ const APPLY = (() => {
initDocRewriteObserver(); initDocRewriteObserver();
} }
if (retiredStyleTimers.size) {
setTimeout(() => {
for (const [id, timer] of retiredStyleTimers.entries()) {
removeStyle({id});
clearTimeout(timer);
}
});
}
updateExposeIframes(); updateExposeIframes();
if (styles.length) { updateCount();
updateCount(); done();
}
} }
function applySections(styleId, code) { function applySections(id, code) {
const id = ID_PREFIX + styleId; let el = styleElements.get(id) || document.getElementById(ID_PREFIX + id);
let el = styleElements.get(id) || document.getElementById(id); if (el && CHROME < 3321) {
if (el && el.textContent !== code) { // workaround for Chrome devtools bug fixed in v65
if (CHROME < 3321) { el.remove();
// workaround for Chrome devtools bug fixed in v65 el = null;
el.remove();
el = null;
} else if (FF_BUG461) {
pageContextQueue.push({id: el.id, el, code});
} else {
el.textContent = code;
}
} }
if (!el) { if (!el) {
if (document.documentElement instanceof SVGSVGElement) { if (document.documentElement instanceof SVGSVGElement) {
@ -320,19 +347,17 @@ const APPLY = (() => {
// HTML document style; also works on HTML-embedded SVG // HTML document style; also works on HTML-embedded SVG
el = document.createElement('style'); el = document.createElement('style');
} }
el.id = id; el.id = ID_PREFIX + id;
el.type = 'text/css'; el.type = 'text/css';
// SVG className is not a string, but an instance of SVGAnimatedString // SVG className is not a string, but an instance of SVGAnimatedString
el.classList.add('stylus'); el.classList.add('stylus');
if (FF_BUG461) {
pageContextQueue.push({id: el.id, el, code});
} else {
el.textContent = code;
}
addStyleElement(el); addStyleElement(el);
} }
if (el.textContent !== code) {
setStyleContent(el, code);
}
styleElements.set(id, el); styleElements.set(id, el);
disabledElements.delete(Number(styleId)); disabledElements.delete(id);
return el; return el;
} }
@ -370,12 +395,12 @@ const APPLY = (() => {
oldStyles.forEach(el => (el.id += '-ghost')); oldStyles.forEach(el => (el.id += '-ghost'));
styleElements.clear(); styleElements.clear();
disabledElements.clear(); disabledElements.clear();
[...retiredStyleTimers.values()].forEach(clearTimeout);
retiredStyleTimers.clear();
applyStyles(newStyles); applyStyles(newStyles);
const removeOld = () => oldStyles.forEach(el => el.remove());
if (docRewriteObserver) { if (docRewriteObserver) {
docRootObserver.evade(() => docRootObserver.evade(removeOld);
oldStyles.forEach(el => el.remove())); } else {
removeOld();
} }
} }
@ -400,7 +425,10 @@ const APPLY = (() => {
return parseInt(el.id.substr(ID_PREFIX.length)); return parseInt(el.id.substr(ID_PREFIX.length));
} }
function orphanCheck() { function orphanCheck(e) {
if (e && e.detail.method !== 'orphan') {
return;
}
if (chrome.i18n && chrome.i18n.getUILanguage()) { if (chrome.i18n && chrome.i18n.getUILanguage()) {
return true; return true;
} }