This commit is contained in:
tophf 2017-12-09 04:27:09 +03:00
parent aff4707bf0
commit 62053316a2
2 changed files with 149 additions and 141 deletions

View File

@ -2,7 +2,8 @@
'use strict'; 'use strict';
// eslint-disable-next-line no-var // eslint-disable-next-line no-var
var styleViaAPI = !CHROME && (() => { var styleViaAPI = !CHROME &&
(() => {
const ACTIONS = { const ACTIONS = {
styleApply, styleApply,
styleDeleted, styleDeleted,
@ -14,7 +15,7 @@ var styleViaAPI = !CHROME && (() => {
}; };
const NOP = Promise.resolve(new Error('NOP')); const NOP = Promise.resolve(new Error('NOP'));
const PONG = Promise.resolve(true); const PONG = Promise.resolve(true);
const onError = () => {}; const onError = () => NOP;
/* <tabId>: Object /* <tabId>: Object
<frameId>: Object <frameId>: Object
@ -34,98 +35,65 @@ var styleViaAPI = !CHROME && (() => {
cache, cache,
}; };
//////////////////// public
function process(request, sender) { function process(request, sender) {
const action = ACTIONS[request.action || request.method]; const action = ACTIONS[request.action || request.method];
if (!action) { return !action ? NOP :
return NOP; isNaN(sender.frameId) && maybeProcessAllFrames(request, sender) ||
} (action(request, sender) || NOP)
const {frameId, tab: {id: tabId}} = sender;
if (isNaN(frameId)) {
const frameIds = Object.keys(allFrameUrls.get(tabId) || {});
if (frameIds.length > 1) {
return Promise.all(
frameIds.map(frameId =>
process(request, Object.assign({}, sender, {frameId: Number(frameId)}))));
}
sender.frameId = 0;
}
return action(request, sender)
.catch(onError) .catch(onError)
.then(maybeToggleObserver); .then(maybeToggleObserver);
} }
function styleApply({ function getFrameUrl(tabId, frameId = 0) {
id = null, const frameUrls = allFrameUrls.get(tabId);
ignoreUrlCheck, return frameUrls && frameUrls[frameId] || '';
}, {
tab,
frameId,
url = getFrameUrl(tab.id, frameId),
}) {
if (prefs.get('disableAll')) {
return NOP;
} }
function setFrameUrl(tabId, frameId, url) {
const frameUrls = allFrameUrls.get(tabId);
if (frameUrls) {
frameUrls[frameId] = url;
} else {
allFrameUrls.set(tabId, {[frameId]: url});
}
}
//////////////////// actions
function styleApply({id = null, styles, ignoreUrlCheck}, sender) {
if (prefs.get('disableAll')) {
return;
}
const {tab, frameId, url = getFrameUrl(tab.id, frameId)} = sender;
const {tabFrames, frameStyles} = getCachedData(tab.id, frameId); const {tabFrames, frameStyles} = getCachedData(tab.id, frameId);
if (id === null && !ignoreUrlCheck && frameStyles.url === url) { if (id === null && !ignoreUrlCheck && frameStyles.url === url) {
return NOP; return;
} }
return getStyles({id, matchUrl: url, enabled: true, asHash: true}).then(styles => { const apply = styles => {
const tasks = []; const newFrameStyles = buildNewFrameStyles(styles, frameStyles);
for (const styleId in styles) { if (newFrameStyles) {
if (isNaN(parseInt(styleId))) { tabFrames[frameId] = newFrameStyles;
continue;
}
// shallow-extract code from the sections array in order to reuse references
// in other places whereas the combined string gets garbage-collected
const styleSections = styles[styleId].map(section => section.code);
const code = styleSections.join('\n');
if (!code) {
delete frameStyles[styleId];
continue;
}
if (code === (frameStyles[styleId] || []).join('\n')) {
continue;
}
frameStyles[styleId] = styleSections;
tasks.push(
browser.tabs.insertCSS(tab.id, {
code,
frameId,
runAt: 'document_start',
matchAboutBlank: true,
}).catch(onError));
}
Object.defineProperty(frameStyles, 'url', {value: url, configurable: true});
tabFrames[frameId] = frameStyles;
cache.set(tab.id, tabFrames); cache.set(tab.id, tabFrames);
return Promise.all(tasks); return replaceCSS(tab.id, frameId, frameStyles, newFrameStyles);
}); }
};
return styles ? apply(styles) || NOP :
getStyles({id, matchUrl: url, enabled: true, asHash: true}).then(apply);
} }
function styleDeleted({id}, {tab, frameId}) { function styleDeleted({id}, {tab, frameId}) {
const {frameStyles, styleSections} = getCachedData(tab.id, frameId, id); const {frameStyles, styleSections} = getCachedData(tab.id, frameId, id);
const code = styleSections.join('\n'); if (styleSections.length) {
if (code && !duplicateCodeExists({frameStyles, id, code})) { const oldFrameStyles = Object.assign({}, frameStyles);
return removeCSS(tab.id, frameId, code).then(() => {
delete frameStyles[id]; delete frameStyles[id];
}); return replaceCSS(tab.id, frameId, oldFrameStyles, frameStyles);
} else {
return NOP;
} }
} }
function styleUpdated({style}, sender) { function styleUpdated({style}, sender) {
if (!style.enabled) { return (style.enabled ? styleApply : styleDeleted)(style, sender);
return styleDeleted(style, sender);
}
const {tab, frameId} = sender;
const {frameStyles, styleSections} = getCachedData(tab.id, frameId, style.id);
const code = styleSections.join('\n');
return styleApply(style, sender).then(code && (() => {
if (!duplicateCodeExists({frameStyles, code, id: null})) {
return removeCSS(tab.id, frameId, code);
}
}));
} }
function styleAdded({style}, sender) { function styleAdded({style}, sender) {
@ -133,31 +101,13 @@ var styleViaAPI = !CHROME && (() => {
} }
function styleReplaceAll(request, sender) { function styleReplaceAll(request, sender) {
const {tab, frameId} = sender; request.ignoreUrlCheck = true;
const oldStylesCode = getFrameStylesJoined(sender); return styleApply(request, sender);
return styleApply({ignoreUrlCheck: true}, sender).then(() => {
const newStylesCode = getFrameStylesJoined(sender);
const tasks = oldStylesCode
.filter(code => !newStylesCode.includes(code))
.map(code => removeCSS(tab.id, frameId, code));
return Promise.all(tasks);
});
} }
function prefChanged({prefs}, sender) { function prefChanged({prefs}, sender) {
if ('disableAll' in prefs) { if ('disableAll' in prefs) {
if (!prefs.disableAll) { disableAll(prefs.disableAll, sender);
return styleApply({}, sender);
}
const {tab, frameId} = sender;
const {tabFrames, frameStyles} = getCachedData(tab.id, frameId);
if (isEmpty(frameStyles)) {
return NOP;
}
delete tabFrames[frameId];
const tasks = Object.keys(frameStyles)
.map(id => removeCSS(tab.id, frameId, frameStyles[id].join('\n')));
return Promise.all(tasks);
} else { } else {
return NOP; return NOP;
} }
@ -167,7 +117,20 @@ var styleViaAPI = !CHROME && (() => {
return PONG; return PONG;
} }
/* utilities */ //////////////////// action helpers
function disableAll(state, sender) {
if (state) {
const {tab, frameId} = sender;
const {tabFrames, frameStyles} = getCachedData(tab.id, frameId);
delete tabFrames[frameId];
return removeCSS(tab.id, frameId, frameStyles);
} else {
return styleApply({ignoreUrlCheck: true}, sender);
}
}
//////////////////// observer
function maybeToggleObserver(passthru) { function maybeToggleObserver(passthru) {
let method; let method;
@ -208,6 +171,87 @@ var styleViaAPI = !CHROME && (() => {
onTabRemoved(removedTabId); onTabRemoved(removedTabId);
} }
//////////////////// browser API
function replaceCSS(tabId, frameId, oldStyles, newStyles) {
return insertCSS(tabId, frameId, newStyles).then(() =>
removeCSS(tabId, frameId, oldStyles));
}
function insertCSS(tabId, frameId, frameStyles) {
const code = getFrameCode(frameStyles);
return code && browser.tabs.insertCSS(tabId, {
// we cache a shallow copy of code from the sections array in order to reuse references
// in other places whereas the combined string gets garbage-collected
code,
frameId,
runAt: 'document_start',
matchAboutBlank: true,
}).catch(onError);
}
function removeCSS(tabId, frameId, frameStyles) {
const code = getFrameCode(frameStyles);
return code && browser.tabs.removeCSS(tabId, {
code,
frameId,
matchAboutBlank: true
}).catch(onError);
}
//////////////////// utilities
function maybeProcessAllFrames(request, sender) {
const {tab} = sender;
const frameIds = Object.keys(allFrameUrls.get(tab.id) || {});
if (frameIds.length <= 1) {
sender.frameId = 0;
return false;
} else {
return Promise.all(
frameIds.map(frameId =>
process(request, {tab, sender: {frameId: Number(frameId)}})));
}
}
function buildNewFrameStyles(styles, oldStyles, url) {
let allSame = true;
let newStyles = {};
for (const sections of getSortedById(styles)) {
const cachedSections = oldStyles[sections.id] || [];
const newSections = [];
let i = 0;
allSame &= sections.length === cachedSections.length;
for (const {code} of sections) {
allSame = allSame ? code === cachedSections[i] : allSame;
newSections[i++] = code;
}
newStyles[sections.id] = newSections;
}
if (!allSame) {
newStyles = Object.assign({}, oldStyles, newStyles);
defineProperty(newStyles, 'url', url);
return newStyles;
}
}
function getSortedById(styleHash) {
const styles = [];
let needsSorting = false;
let prevKey = -1;
for (let k in styleHash) {
k = parseInt(k);
if (!isNaN(k)) {
const sections = styleHash[k];
styles.push(sections);
Object.defineProperty(sections, 'id', {value: k});
needsSorting |= k < prevKey;
prevKey = k;
}
}
return needsSorting ? styles.sort((a, b) => a.id - b.id) : styles;
}
function getCachedData(tabId, frameId, styleId) { function getCachedData(tabId, frameId, styleId) {
const tabFrames = cache.get(tabId) || {}; const tabFrames = cache.get(tabId) || {};
const frameStyles = tabFrames[frameId] || {}; const frameStyles = tabFrames[frameId] || {};
@ -215,48 +259,12 @@ var styleViaAPI = !CHROME && (() => {
return {tabFrames, frameStyles, styleSections}; return {tabFrames, frameStyles, styleSections};
} }
function getFrameUrl(tabId, frameId = 0) { function getFrameCode(frameStyles) {
const frameUrls = allFrameUrls.get(tabId); return [].concat(...getSortedById(frameStyles)).join('\n');
return frameUrls && frameUrls[frameId] || '';
} }
function setFrameUrl(tabId, frameId, url) { function defineProperty(obj, name, value) {
const frameUrls = allFrameUrls.get(tabId); return Object.defineProperty(obj, name, {value, configurable: true});
if (frameUrls) {
frameUrls[frameId] = url;
} else {
allFrameUrls.set(tabId, {[frameId]: url});
}
}
function getFrameStylesJoined({
tab,
frameId,
frameStyles = getCachedData(tab.id, frameId).frameStyles,
}) {
return Object.keys(frameStyles).map(id => frameStyles[id].join('\n'));
}
function duplicateCodeExists({
tab,
frameId,
frameStyles = getCachedData(tab.id, frameId).frameStyles,
frameStylesCode = {},
id,
code = frameStylesCode[id] || frameStyles[id].join('\n'),
}) {
id = String(id);
for (const styleId in frameStyles) {
if (id !== styleId &&
code === (frameStylesCode[styleId] || frameStyles[styleId].join('\n'))) {
return true;
}
}
}
function removeCSS(tabId, frameId, code) {
return browser.tabs.removeCSS(tabId, {frameId, code, matchAboutBlank: true})
.catch(onError);
} }
function isEmpty(obj) { function isEmpty(obj) {

View File

@ -152,9 +152,9 @@ function sendMessage(msg, callback) {
const {tabId, frameId} = msg; const {tabId, frameId} = msg;
if (tabId >= 0 && FIREFOX) { if (tabId >= 0 && FIREFOX) {
// FF: reroute all tabs messages to styleViaAPI // FF: reroute all tabs messages to styleViaAPI
const msgInBG = BG === window ? msg : BG.deepCopy(msg); const msgForBG = BG === window ? msg : BG.deepCopy(msg);
const sender = {tab: {id: tabId}, frameId}; const sender = {tab: {id: tabId}, frameId};
const task = BG.styleViaAPI.process(msgInBG, sender); const task = BG.styleViaAPI.process(msgForBG, sender);
return callback ? task.then(callback) : task; return callback ? task.then(callback) : task;
} }
const fn = tabId >= 0 ? chrome.tabs.sendMessage : chrome.runtime.sendMessage; const fn = tabId >= 0 ? chrome.tabs.sendMessage : chrome.runtime.sendMessage;