WIP2
This commit is contained in:
parent
62053316a2
commit
7d094847f6
|
@ -193,18 +193,37 @@ window.addEventListener('storageReady', function _() {
|
|||
|
||||
updateIcon({id: undefined}, {});
|
||||
|
||||
if (FIREFOX) {
|
||||
queryTabs().then(tabs =>
|
||||
tabs.forEach(tab => {
|
||||
if (!tab.width) {
|
||||
// skip lazy-loaded tabs (width = 0) that seem to start loading on message
|
||||
return;
|
||||
}
|
||||
const tabId = tab.id;
|
||||
const frameUrls = {0: tab.url};
|
||||
styleViaAPI.allFrameUrls.set(tabId, frameUrls);
|
||||
chrome.webNavigation.getAllFrames({tabId}, frames => frames &&
|
||||
frames.forEach(({frameId, parentFrameId, url}) => {
|
||||
if (frameId) {
|
||||
frameUrls[frameId] = url === 'about:blank' ? frameUrls[parentFrameId] : url;
|
||||
}
|
||||
}));
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const NTP = 'chrome://newtab/';
|
||||
const ALL_URLS = '<all_urls>';
|
||||
const contentScripts = chrome.runtime.getManifest().content_scripts;
|
||||
if (!FIREFOX) {
|
||||
contentScripts.push({
|
||||
js: ['content/apply.js'],
|
||||
matches: ['<all_urls>'],
|
||||
run_at: 'document_start',
|
||||
match_about_blank: true,
|
||||
all_frames: true
|
||||
});
|
||||
}
|
||||
contentScripts.push({
|
||||
js: ['content/apply.js'],
|
||||
matches: ['<all_urls>'],
|
||||
run_at: 'document_start',
|
||||
match_about_blank: true,
|
||||
all_frames: true
|
||||
});
|
||||
|
||||
// expand * as .*?
|
||||
const wildcardAsRegExp = (s, flags) => new RegExp(
|
||||
s.replace(/[{}()[\]/\\.+?^$:=!|]/g, '\\$&')
|
||||
|
@ -236,23 +255,9 @@ window.addEventListener('storageReady', function _() {
|
|||
};
|
||||
|
||||
queryTabs().then(tabs =>
|
||||
tabs.forEach(tab => {
|
||||
if (FIREFOX) {
|
||||
const tabId = tab.id;
|
||||
const frameUrls = {'0': tab.url};
|
||||
styleViaAPI.allFrameUrls.set(tabId, frameUrls);
|
||||
chrome.webNavigation.getAllFrames({tabId}, frames => frames &&
|
||||
frames.forEach(({frameId, parentFrameId, url}) => {
|
||||
if (frameId) {
|
||||
frameUrls[frameId] = url === 'about:blank' ? frameUrls[parentFrameId] : url;
|
||||
}
|
||||
}));
|
||||
} else if (tab.width) {
|
||||
// skip lazy-loaded aka unloaded tabs that seem to start loading on message
|
||||
contentScripts.forEach(cs =>
|
||||
setTimeout(pingCS, 0, cs, tab));
|
||||
}
|
||||
}));
|
||||
tabs.forEach(tab => tab.width &&
|
||||
contentScripts.forEach(cs =>
|
||||
setTimeout(pingCS, 0, cs, tab))));
|
||||
});
|
||||
|
||||
// *************************************************************************
|
||||
|
@ -304,22 +309,27 @@ function webNavigationListenerChrome(method, data) {
|
|||
|
||||
function webNavigationListenerFF(method, data) {
|
||||
const {tabId, frameId, url} = data;
|
||||
if (url !== 'about:blank' || !frameId) {
|
||||
//console.log(method, data);
|
||||
if (frameId === 0 || url !== 'about:blank') {
|
||||
if ((!method || method === 'styleApply') &&
|
||||
styleViaAPI.getFrameUrl(tabId, frameId) !== url) {
|
||||
styleViaAPI.cache.delete(tabId);
|
||||
}
|
||||
styleViaAPI.setFrameUrl(tabId, frameId, url);
|
||||
webNavigationListener(method, data);
|
||||
return;
|
||||
}
|
||||
const frames = styleViaAPI.allFrameUrls.get(tabId);
|
||||
if (Object.keys(frames).length === 1) {
|
||||
frames[frameId] = frames['0'];
|
||||
webNavigationListener(method, data);
|
||||
return;
|
||||
}
|
||||
chrome.webNavigation.getFrame({tabId, frameId}, info => {
|
||||
const hasParent = !chrome.runtime.lastError && info.parentFrameId >= 0;
|
||||
frames[frameId] = hasParent ? frames[info.parentFrameId] : url;
|
||||
webNavigationListener(method, data);
|
||||
});
|
||||
//const frames = styleViaAPI.allFrameUrls.get(tabId);
|
||||
//if (Object.keys(frames).length === 1) {
|
||||
// frames[frameId] = frames['0'];
|
||||
// webNavigationListener(method, data);
|
||||
// return;
|
||||
//}
|
||||
//chrome.webNavigation.getFrame({tabId, frameId}, info => {
|
||||
// const hasParent = !chrome.runtime.lastError && info.parentFrameId >= 0;
|
||||
// frames[frameId] = hasParent ? frames[info.parentFrameId] : url;
|
||||
// webNavigationListener(method, data);
|
||||
//});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ var styleViaAPI = !CHROME &&
|
|||
styleDeleted,
|
||||
styleUpdated,
|
||||
styleAdded,
|
||||
styleReplaceAll,
|
||||
styleReplaceAll: styleApply,
|
||||
prefChanged,
|
||||
ping,
|
||||
};
|
||||
|
@ -17,15 +17,11 @@ var styleViaAPI = !CHROME &&
|
|||
const PONG = Promise.resolve(true);
|
||||
const onError = () => NOP;
|
||||
|
||||
/* <tabId>: Object
|
||||
<frameId>: Object
|
||||
url: String, non-enumerable
|
||||
<styleId>: Array of strings
|
||||
section code */
|
||||
const cache = new Map();
|
||||
const allFrameUrls = new Map();
|
||||
|
||||
let observingTabs = false;
|
||||
chrome.tabs.onRemoved.addListener(onTabRemoved);
|
||||
chrome.tabs.onReplaced.addListener(onTabReplaced);
|
||||
|
||||
return {
|
||||
process,
|
||||
|
@ -35,15 +31,23 @@ var styleViaAPI = !CHROME &&
|
|||
cache,
|
||||
};
|
||||
|
||||
//////////////////// public
|
||||
//region public methods
|
||||
|
||||
function process(request, sender) {
|
||||
console.log(request.action || request.method, request.prefs || request.styles || request.style, sender.tab, sender.frameId);
|
||||
const action = ACTIONS[request.action || request.method];
|
||||
return !action ? NOP :
|
||||
isNaN(sender.frameId) && maybeProcessAllFrames(request, sender) ||
|
||||
(action(request, sender) || NOP)
|
||||
.catch(onError)
|
||||
.then(maybeToggleObserver);
|
||||
if (!action) {
|
||||
return NOP;
|
||||
}
|
||||
const {tab} = sender;
|
||||
if (!isNaN(sender.frameId)) {
|
||||
const result = action(request, sender);
|
||||
return result ? result.catch(onError) : NOP;
|
||||
}
|
||||
return browser.webNavigation.getAllFrames({tabId: tab.id}).then(frames =>
|
||||
Promise.all((frames || []).map(({frameId}) =>
|
||||
(action(request, {tab, frameId}) || NOP).catch(onError)))
|
||||
).catch(onError);
|
||||
}
|
||||
|
||||
function getFrameUrl(tabId, frameId = 0) {
|
||||
|
@ -60,35 +64,33 @@ var styleViaAPI = !CHROME &&
|
|||
}
|
||||
}
|
||||
|
||||
//////////////////// actions
|
||||
//endregion
|
||||
//region actions
|
||||
|
||||
function styleApply({id = null, styles, ignoreUrlCheck}, sender) {
|
||||
if (prefs.get('disableAll')) {
|
||||
function styleApply({styles, disableAll}, sender) {
|
||||
if (disableAll) {
|
||||
return;
|
||||
}
|
||||
const {tab, frameId, url = getFrameUrl(tab.id, frameId)} = sender;
|
||||
const {tabFrames, frameStyles} = getCachedData(tab.id, frameId);
|
||||
if (id === null && !ignoreUrlCheck && frameStyles.url === url) {
|
||||
return;
|
||||
const {tab: {id: tabId}, frameId, url} = sender;
|
||||
if (!styles || styles === 'DIY') {
|
||||
return requestStyles({matchUrl: url || getFrameUrl(tabId, frameId)}, sender);
|
||||
}
|
||||
const {tabFrames, frameStyles} = getCachedData(tabId, frameId);
|
||||
const newSorted = getSortedById(styles);
|
||||
if (!sameArrays(frameStyles, newSorted, sameArrays)) {
|
||||
tabFrames[frameId] = newSorted;
|
||||
cache.set(tabId, tabFrames);
|
||||
return replaceCSS(tabId, frameId, frameStyles, newSorted);
|
||||
}
|
||||
const apply = styles => {
|
||||
const newFrameStyles = buildNewFrameStyles(styles, frameStyles);
|
||||
if (newFrameStyles) {
|
||||
tabFrames[frameId] = newFrameStyles;
|
||||
cache.set(tab.id, tabFrames);
|
||||
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}) {
|
||||
const {frameStyles, styleSections} = getCachedData(tab.id, frameId, id);
|
||||
if (styleSections.length) {
|
||||
const oldFrameStyles = Object.assign({}, frameStyles);
|
||||
delete frameStyles[id];
|
||||
return replaceCSS(tab.id, frameId, oldFrameStyles, frameStyles);
|
||||
const {frameStyles} = getCachedData(tab.id, frameId);
|
||||
const index = frameStyles.findIndex(item => item.id === id);
|
||||
if (index >= 0) {
|
||||
const oldStyles = frameStyles.slice();
|
||||
frameStyles.splice(index, 1);
|
||||
return replaceCSS(tab.id, frameId, oldStyles, frameStyles);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,20 +98,13 @@ var styleViaAPI = !CHROME &&
|
|||
return (style.enabled ? styleApply : styleDeleted)(style, sender);
|
||||
}
|
||||
|
||||
function styleAdded({style}, sender) {
|
||||
return style.enabled ? styleApply(style, sender) : NOP;
|
||||
}
|
||||
|
||||
function styleReplaceAll(request, sender) {
|
||||
request.ignoreUrlCheck = true;
|
||||
return styleApply(request, sender);
|
||||
function styleAdded({style: {enabled}}, sender) {
|
||||
return enabled && styleApply({}, sender);
|
||||
}
|
||||
|
||||
function prefChanged({prefs}, sender) {
|
||||
if ('disableAll' in prefs) {
|
||||
disableAll(prefs.disableAll, sender);
|
||||
} else {
|
||||
return NOP;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,7 +112,8 @@ var styleViaAPI = !CHROME &&
|
|||
return PONG;
|
||||
}
|
||||
|
||||
//////////////////// action helpers
|
||||
//endregion
|
||||
//region action helpers
|
||||
|
||||
function disableAll(state, sender) {
|
||||
if (state) {
|
||||
|
@ -126,113 +122,60 @@ var styleViaAPI = !CHROME &&
|
|||
delete tabFrames[frameId];
|
||||
return removeCSS(tab.id, frameId, frameStyles);
|
||||
} else {
|
||||
return styleApply({ignoreUrlCheck: true}, sender);
|
||||
return styleApply({}, sender);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////// observer
|
||||
|
||||
function maybeToggleObserver(passthru) {
|
||||
let method;
|
||||
if (!observingTabs && cache.size) {
|
||||
method = 'addListener';
|
||||
} else if (observingTabs && !cache.size) {
|
||||
method = 'removeListener';
|
||||
} else {
|
||||
return passthru;
|
||||
}
|
||||
observingTabs = !observingTabs;
|
||||
chrome.webNavigation.onCommitted[method](onNavigationCommitted);
|
||||
chrome.tabs.onRemoved[method](onTabRemoved);
|
||||
chrome.tabs.onReplaced[method](onTabReplaced);
|
||||
return passthru;
|
||||
}
|
||||
|
||||
function onNavigationCommitted({tabId, frameId}) {
|
||||
if (frameId === 0) {
|
||||
onTabRemoved(tabId);
|
||||
return;
|
||||
}
|
||||
const tabFrames = cache.get(tabId);
|
||||
if (tabFrames && frameId in tabFrames) {
|
||||
delete tabFrames[frameId];
|
||||
if (isEmpty(tabFrames)) {
|
||||
onTabRemoved(tabId);
|
||||
}
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
//region observer
|
||||
|
||||
function onTabRemoved(tabId) {
|
||||
cache.delete(tabId);
|
||||
maybeToggleObserver();
|
||||
}
|
||||
|
||||
function onTabReplaced(addedTabId, removedTabId) {
|
||||
onTabRemoved(removedTabId);
|
||||
cache.delete(removedTabId);
|
||||
}
|
||||
|
||||
//////////////////// browser API
|
||||
//endregion
|
||||
//region browser API
|
||||
|
||||
function replaceCSS(tabId, frameId, oldStyles, newStyles) {
|
||||
console.log.apply(null, arguments);
|
||||
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);
|
||||
return !code ? NOP :
|
||||
browser.tabs.insertCSS(tabId, {
|
||||
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);
|
||||
return !code ? NOP :
|
||||
browser.tabs.removeCSS(tabId, {
|
||||
code,
|
||||
frameId,
|
||||
matchAboutBlank: true
|
||||
}).catch(onError);
|
||||
}
|
||||
|
||||
//////////////////// utilities
|
||||
//endregion
|
||||
//region 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 requestStyles(options, sender) {
|
||||
options.matchUrl = options.matchUrl || sender.url;
|
||||
options.enabled = true;
|
||||
options.asHash = true;
|
||||
return getStyles(options).then(styles =>
|
||||
styleApply({styles}, sender));
|
||||
}
|
||||
|
||||
function getSortedById(styleHash) {
|
||||
|
@ -242,9 +185,9 @@ var styleViaAPI = !CHROME &&
|
|||
for (let k in styleHash) {
|
||||
k = parseInt(k);
|
||||
if (!isNaN(k)) {
|
||||
const sections = styleHash[k];
|
||||
const sections = styleHash[k].map(({code}) => code);
|
||||
styles.push(sections);
|
||||
Object.defineProperty(sections, 'id', {value: k});
|
||||
defineProperty(sections, 'id', k);
|
||||
needsSorting |= k < prevKey;
|
||||
prevKey = k;
|
||||
}
|
||||
|
@ -254,23 +197,24 @@ var styleViaAPI = !CHROME &&
|
|||
|
||||
function getCachedData(tabId, frameId, styleId) {
|
||||
const tabFrames = cache.get(tabId) || {};
|
||||
const frameStyles = tabFrames[frameId] || {};
|
||||
const styleSections = styleId && frameStyles[styleId] || [];
|
||||
const frameStyles = tabFrames[frameId] || [];
|
||||
const styleSections = styleId && frameStyles.find(s => s.id === styleId) || [];
|
||||
return {tabFrames, frameStyles, styleSections};
|
||||
}
|
||||
|
||||
function getFrameCode(frameStyles) {
|
||||
return [].concat(...getSortedById(frameStyles)).join('\n');
|
||||
// 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
|
||||
return typeof frameStyles === 'string' ? frameStyles : [].concat(...frameStyles).join('\n');
|
||||
}
|
||||
|
||||
function defineProperty(obj, name, value) {
|
||||
return Object.defineProperty(obj, name, {value, configurable: true});
|
||||
}
|
||||
|
||||
function isEmpty(obj) {
|
||||
for (const k in obj) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
function sameArrays(a, b, fn) {
|
||||
return a.length === b.length && a.every((el, i) => fn ? fn(el, b[i]) : el === b[i]);
|
||||
}
|
||||
|
||||
//endregion
|
||||
})();
|
||||
|
|
Loading…
Reference in New Issue
Block a user