FF: support private/container tabs
This commit is contained in:
parent
62e333a0ba
commit
3418ac9cb9
10
.eslintrc
10
.eslintrc
|
@ -13,9 +13,11 @@ globals:
|
||||||
KEEP_CHANNEL_OPEN: false
|
KEEP_CHANNEL_OPEN: false
|
||||||
CHROME: false
|
CHROME: false
|
||||||
FIREFOX: false
|
FIREFOX: false
|
||||||
|
VIVALDI: false
|
||||||
OPERA: false
|
OPERA: false
|
||||||
URLS: false
|
URLS: false
|
||||||
BG: false
|
BG: false
|
||||||
|
API: false
|
||||||
notifyAllTabs: false
|
notifyAllTabs: false
|
||||||
sendMessage: false
|
sendMessage: false
|
||||||
queryTabs: false
|
queryTabs: false
|
||||||
|
@ -33,10 +35,6 @@ globals:
|
||||||
tryJSONparse: false
|
tryJSONparse: false
|
||||||
debounce: false
|
debounce: false
|
||||||
deepCopy: false
|
deepCopy: false
|
||||||
onBackgroundReady: false
|
|
||||||
deleteStyleSafe: false
|
|
||||||
getStylesSafe: false
|
|
||||||
saveStyleSafe: false
|
|
||||||
sessionStorageHash: false
|
sessionStorageHash: false
|
||||||
download: false
|
download: false
|
||||||
invokeOrPostpone: false
|
invokeOrPostpone: false
|
||||||
|
@ -63,6 +61,10 @@ globals:
|
||||||
# prefs.js
|
# prefs.js
|
||||||
prefs: false
|
prefs: false
|
||||||
setupLivePrefs: false
|
setupLivePrefs: false
|
||||||
|
# storage-util.js
|
||||||
|
chromeLocal: false
|
||||||
|
chromeSync: false
|
||||||
|
LZString: false
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
accessor-pairs: [2]
|
accessor-pairs: [2]
|
||||||
|
|
|
@ -1,9 +1,38 @@
|
||||||
/* global dbExec, getStyles, saveStyle */
|
/*
|
||||||
/* global handleCssTransitionBug */
|
global dbExec getStyles saveStyle deleteStyle
|
||||||
/* global usercssHelper openEditor */
|
global handleCssTransitionBug detectSloppyRegexps
|
||||||
/* global styleViaAPI */
|
global openEditor
|
||||||
|
global styleViaAPI
|
||||||
|
global loadScript
|
||||||
|
global updater
|
||||||
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var API_METHODS = {
|
||||||
|
|
||||||
|
getStyles,
|
||||||
|
saveStyle,
|
||||||
|
deleteStyle,
|
||||||
|
|
||||||
|
download: msg => download(msg.url),
|
||||||
|
getPrefs: () => prefs.getAll(),
|
||||||
|
healthCheck: () => dbExec().then(() => true),
|
||||||
|
|
||||||
|
detectSloppyRegexps,
|
||||||
|
openEditor,
|
||||||
|
updateIcon,
|
||||||
|
|
||||||
|
closeTab: (msg, sender, respond) => {
|
||||||
|
chrome.tabs.remove(msg.tabId || sender.tab.id, () => {
|
||||||
|
if (chrome.runtime.lastError && msg.tabId !== sender.tab.id) {
|
||||||
|
respond(new Error(chrome.runtime.lastError.message));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return KEEP_CHANNEL_OPEN;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
var browserCommands, contextMenus;
|
var browserCommands, contextMenus;
|
||||||
|
|
||||||
|
@ -55,9 +84,17 @@ if (!chrome.browserAction ||
|
||||||
window.updateIcon = () => {};
|
window.updateIcon = () => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tabIcons = new Map();
|
||||||
|
chrome.tabs.onRemoved.addListener(tabId => tabIcons.delete(tabId));
|
||||||
|
chrome.tabs.onReplaced.addListener((added, removed) => tabIcons.delete(removed));
|
||||||
|
|
||||||
// *************************************************************************
|
// *************************************************************************
|
||||||
// set the default icon displayed after a tab is created until webNavigation kicks in
|
// set the default icon displayed after a tab is created until webNavigation kicks in
|
||||||
prefs.subscribe(['iconset'], () => updateIcon({id: undefined}, {}));
|
prefs.subscribe(['iconset'], () =>
|
||||||
|
updateIcon({
|
||||||
|
tab: {id: undefined},
|
||||||
|
styles: {},
|
||||||
|
}));
|
||||||
|
|
||||||
// *************************************************************************
|
// *************************************************************************
|
||||||
{
|
{
|
||||||
|
@ -160,7 +197,10 @@ if (chrome.contextMenus) {
|
||||||
window.addEventListener('storageReady', function _() {
|
window.addEventListener('storageReady', function _() {
|
||||||
window.removeEventListener('storageReady', _);
|
window.removeEventListener('storageReady', _);
|
||||||
|
|
||||||
updateIcon({id: undefined}, {});
|
updateIcon({
|
||||||
|
tab: {id: undefined},
|
||||||
|
styles: {},
|
||||||
|
});
|
||||||
|
|
||||||
const NTP = 'chrome://newtab/';
|
const NTP = 'chrome://newtab/';
|
||||||
const ALL_URLS = '<all_urls>';
|
const ALL_URLS = '<all_urls>';
|
||||||
|
@ -223,7 +263,8 @@ function webNavigationListener(method, {url, tabId, frameId}) {
|
||||||
}
|
}
|
||||||
// main page frame id is 0
|
// main page frame id is 0
|
||||||
if (frameId === 0) {
|
if (frameId === 0) {
|
||||||
updateIcon({id: tabId, url}, styles);
|
tabIcons.delete(tabId);
|
||||||
|
updateIcon({tab: {id: tabId, url}, styles});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -256,13 +297,13 @@ function webNavUsercssInstallerFF(data) {
|
||||||
getTab(tabId),
|
getTab(tabId),
|
||||||
]).then(([pong, tab]) => {
|
]).then(([pong, tab]) => {
|
||||||
if (pong !== true && tab.url !== 'about:blank') {
|
if (pong !== true && tab.url !== 'about:blank') {
|
||||||
usercssHelper.openInstallPage(tab, {direct: true});
|
API_METHODS.installUsercss({direct: true}, {tab});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function updateIcon(tab, styles) {
|
function updateIcon({tab, styles}) {
|
||||||
if (tab.id < 0) {
|
if (tab.id < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -277,38 +318,44 @@ function updateIcon(tab, styles) {
|
||||||
.then(url => getStyles({matchUrl: url, enabled: true, asHash: true}))
|
.then(url => getStyles({matchUrl: url, enabled: true, asHash: true}))
|
||||||
.then(stylesReceived);
|
.then(stylesReceived);
|
||||||
|
|
||||||
|
function countStyles(styles) {
|
||||||
|
if (Array.isArray(styles)) return styles.length;
|
||||||
|
return Object.keys(styles).reduce((sum, id) => sum + !isNaN(Number(id)), 0);
|
||||||
|
}
|
||||||
|
|
||||||
function stylesReceived(styles) {
|
function stylesReceived(styles) {
|
||||||
let numStyles = styles.length;
|
const numStyles = countStyles(styles);
|
||||||
if (numStyles === undefined) {
|
|
||||||
// for 'styles' asHash:true fake the length by counting numeric ids manually
|
|
||||||
numStyles = 0;
|
|
||||||
for (const id of Object.keys(styles)) {
|
|
||||||
numStyles += id.match(/^\d+$/) ? 1 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const disableAll = 'disableAll' in styles ? styles.disableAll : prefs.get('disableAll');
|
const disableAll = 'disableAll' in styles ? styles.disableAll : prefs.get('disableAll');
|
||||||
const postfix = disableAll ? 'x' : numStyles === 0 ? 'w' : '';
|
const postfix = disableAll ? 'x' : numStyles === 0 ? 'w' : '';
|
||||||
const color = prefs.get(disableAll ? 'badgeDisabled' : 'badgeNormal');
|
const color = prefs.get(disableAll ? 'badgeDisabled' : 'badgeNormal');
|
||||||
const text = prefs.get('show-badge') && numStyles ? String(numStyles) : '';
|
const text = prefs.get('show-badge') && numStyles ? String(numStyles) : '';
|
||||||
const iconset = ['', 'light/'][prefs.get('iconset')] || '';
|
const iconset = ['', 'light/'][prefs.get('iconset')] || '';
|
||||||
const path = 'images/icon/' + iconset;
|
const path = 'images/icon/' + iconset;
|
||||||
chrome.browserAction.setIcon({
|
const tabIcon = tabIcons.get(tab.id) || {};
|
||||||
tabId: tab.id,
|
if (tabIcon.iconType !== iconset + postfix) {
|
||||||
path: {
|
tabIcons.set(tab.id, tabIcon);
|
||||||
|
tabIcon.iconType = iconset + postfix;
|
||||||
|
const paths = {};
|
||||||
|
if (FIREFOX || CHROME >= 2883 && !VIVALDI) {
|
||||||
// Material Design 2016 new size is 16px
|
// Material Design 2016 new size is 16px
|
||||||
16: `${path}16${postfix}.png`,
|
paths['16'] = `${path}16${postfix}.png`;
|
||||||
32: `${path}32${postfix}.png`,
|
paths['32'] = `${path}32${postfix}.png`;
|
||||||
|
} else {
|
||||||
// Chromium forks or non-chromium browsers may still use the traditional 19px
|
// Chromium forks or non-chromium browsers may still use the traditional 19px
|
||||||
19: `${path}19${postfix}.png`,
|
paths['19'] = `${path}19${postfix}.png`;
|
||||||
38: `${path}38${postfix}.png`,
|
paths['38'] = `${path}38${postfix}.png`;
|
||||||
// TODO: add Edge preferred sizes: 20, 25, 30, 40
|
|
||||||
},
|
|
||||||
}, () => {
|
|
||||||
if (chrome.runtime.lastError || tab.id === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// Vivaldi bug workaround: setBadgeText must follow setBadgeBackgroundColor
|
chrome.browserAction.setIcon({tabId: tab.id, path: paths}, ignoreChromeError);
|
||||||
|
}
|
||||||
|
if (tab.id === undefined) return;
|
||||||
|
let defaultIcon = tabIcons.get(undefined);
|
||||||
|
if (!defaultIcon) tabIcons.set(undefined, (defaultIcon = {}));
|
||||||
|
if (defaultIcon.color !== color) {
|
||||||
|
defaultIcon.color = color;
|
||||||
chrome.browserAction.setBadgeBackgroundColor({color});
|
chrome.browserAction.setBadgeBackgroundColor({color});
|
||||||
|
}
|
||||||
|
if (tabIcon.text !== text) {
|
||||||
|
tabIcon.text = text;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
getTab(tab.id).then(realTab => {
|
getTab(tab.id).then(realTab => {
|
||||||
// skip pre-rendered tabs
|
// skip pre-rendered tabs
|
||||||
|
@ -317,67 +364,31 @@ function updateIcon(tab, styles) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function onRuntimeMessage(request, sender, sendResponseInternal) {
|
|
||||||
const sendResponse = data => {
|
|
||||||
// wrap Error object instance as {__ERROR__: message} - will be unwrapped in sendMessage
|
|
||||||
if (data instanceof Error) {
|
|
||||||
data = {__ERROR__: data.message};
|
|
||||||
}
|
}
|
||||||
// prevent browser exception bug on sending a response to a closed tab
|
}
|
||||||
tryCatch(sendResponseInternal, data);
|
}
|
||||||
};
|
|
||||||
switch (request.method) {
|
|
||||||
case 'getStyles':
|
function onRuntimeMessage(msg, sender, sendResponse) {
|
||||||
getStyles(request).then(sendResponse);
|
const fn = API_METHODS[msg.method];
|
||||||
return KEEP_CHANNEL_OPEN;
|
if (!fn) return;
|
||||||
|
|
||||||
case 'saveStyle':
|
// wrap 'Error' object instance as {__ERROR__: message},
|
||||||
saveStyle(request).then(sendResponse);
|
// which will be unwrapped by sendMessage,
|
||||||
return KEEP_CHANNEL_OPEN;
|
// and prevent exceptions on sending to a closed tab
|
||||||
|
const respond = data =>
|
||||||
case 'saveUsercss':
|
tryCatch(sendResponse,
|
||||||
usercssHelper.save(request, true).then(sendResponse);
|
data instanceof Error ? {__ERROR__: data.message} : data);
|
||||||
return KEEP_CHANNEL_OPEN;
|
|
||||||
|
const result = fn(msg, sender, respond);
|
||||||
case 'buildUsercss':
|
if (result instanceof Promise) {
|
||||||
usercssHelper.build(request, true).then(sendResponse);
|
result
|
||||||
return KEEP_CHANNEL_OPEN;
|
.catch(e => ({__ERROR__: e instanceof Error ? e.message : e}))
|
||||||
|
.then(respond);
|
||||||
case 'healthCheck':
|
return KEEP_CHANNEL_OPEN;
|
||||||
dbExec()
|
} else if (result === KEEP_CHANNEL_OPEN) {
|
||||||
.then(() => sendResponse(true))
|
return KEEP_CHANNEL_OPEN;
|
||||||
.catch(() => sendResponse(false));
|
} else if (result !== undefined) {
|
||||||
return KEEP_CHANNEL_OPEN;
|
respond(result);
|
||||||
|
|
||||||
case 'styleViaAPI':
|
|
||||||
styleViaAPI(request, sender);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case 'download':
|
|
||||||
download(request.url)
|
|
||||||
.then(sendResponse)
|
|
||||||
.catch(() => sendResponse(null));
|
|
||||||
return KEEP_CHANNEL_OPEN;
|
|
||||||
|
|
||||||
case 'openUsercssInstallPage':
|
|
||||||
usercssHelper.openInstallPage(sender.tab, request).then(sendResponse);
|
|
||||||
return KEEP_CHANNEL_OPEN;
|
|
||||||
|
|
||||||
case 'closeTab':
|
|
||||||
chrome.tabs.remove(request.tabId || sender.tab.id, () => {
|
|
||||||
if (chrome.runtime.lastError && request.tabId !== sender.tab.id) {
|
|
||||||
sendResponse(new Error(chrome.runtime.lastError.message));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return KEEP_CHANNEL_OPEN;
|
|
||||||
|
|
||||||
case 'openEditor':
|
|
||||||
openEditor(request.id);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
100
background/search-db.js
Normal file
100
background/search-db.js
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
/* global API_METHODS filterStyles cachedStyles */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
// toLocaleLowerCase cache, autocleared after 1 minute
|
||||||
|
const cache = new Map();
|
||||||
|
// top-level style properties to be searched
|
||||||
|
const PARTS = {
|
||||||
|
name: searchText,
|
||||||
|
url: searchText,
|
||||||
|
sourceCode: searchText,
|
||||||
|
sections: searchSections,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param params
|
||||||
|
* @param {string} params.query - 1. url:someurl 2. text (may contain quoted parts like "qUot Ed")
|
||||||
|
* @param {number[]} [params.ids] - if not specified, all styles are searched
|
||||||
|
* @returns {number[]} - array of matched styles ids
|
||||||
|
*/
|
||||||
|
API_METHODS.searchDB = ({query, ids}) => {
|
||||||
|
let rx, words, icase, matchUrl;
|
||||||
|
query = query.trim();
|
||||||
|
|
||||||
|
if (/^url:/i.test(query)) {
|
||||||
|
matchUrl = query.slice(query.indexOf(':') + 1).trim();
|
||||||
|
if (matchUrl) {
|
||||||
|
return filterStyles({matchUrl}).map(style => style.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (query.startsWith('/') && /^\/(.+?)\/([gimsuy]*)$/.test(query)) {
|
||||||
|
rx = tryRegExp(RegExp.$1, RegExp.$2);
|
||||||
|
}
|
||||||
|
if (!rx) {
|
||||||
|
words = query
|
||||||
|
.split(/(".*?")|\s+/)
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(w => w.startsWith('"') && w.endsWith('"')
|
||||||
|
? w.slice(1, -1)
|
||||||
|
: w)
|
||||||
|
.filter(w => w.length > 1);
|
||||||
|
words = words.length ? words : [query];
|
||||||
|
icase = words.some(w => w === lower(w));
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
for (const item of ids || cachedStyles.list) {
|
||||||
|
const id = isNaN(item) ? item.id : item;
|
||||||
|
if (!query || words && !words.length) {
|
||||||
|
results.push(id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const style = isNaN(item) ? item : cachedStyles.byId.get(item);
|
||||||
|
if (!style) continue;
|
||||||
|
for (const part in PARTS) {
|
||||||
|
const text = style[part];
|
||||||
|
if (text && PARTS[part](text, rx, words, icase)) {
|
||||||
|
results.push(id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cache.size) debounce(clearCache, 60e3);
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
function searchText(text, rx, words, icase) {
|
||||||
|
if (rx) return rx.test(text);
|
||||||
|
for (let pass = 1; pass <= (icase ? 2 : 1); pass++) {
|
||||||
|
if (words.every(w => text.includes(w))) return true;
|
||||||
|
text = lower(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchSections(sections, rx, words, icase) {
|
||||||
|
for (const section of sections) {
|
||||||
|
for (const prop in section) {
|
||||||
|
const value = section[prop];
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
if (searchText(value, rx, words, icase)) return true;
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
if (value.some(str => searchText(str, rx, words, icase))) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function lower(text) {
|
||||||
|
let result = cache.get(text);
|
||||||
|
if (result) return result;
|
||||||
|
result = text.toLocaleLowerCase();
|
||||||
|
cache.set(text, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearCache() {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
})();
|
|
@ -1,4 +1,4 @@
|
||||||
/* global LZString */
|
/* global getStyleWithNoCode */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const RX_NAMESPACE = new RegExp([/[\s\r\n]*/,
|
const RX_NAMESPACE = new RegExp([/[\s\r\n]*/,
|
||||||
|
@ -29,54 +29,6 @@ var cachedStyles = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
window.LZString = window.LZString || window.LZStringUnsafe;
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
|
||||||
var [chromeLocal, chromeSync] = [
|
|
||||||
chrome.storage.local,
|
|
||||||
chrome.storage.sync,
|
|
||||||
].map(storage => {
|
|
||||||
const wrapper = {
|
|
||||||
get(options) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
storage.get(options, data => resolve(data));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set(data) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
storage.set(data, () => resolve(data));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
remove(keyOrKeys) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
storage.remove(keyOrKeys, resolve);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getValue(key) {
|
|
||||||
return wrapper.get(key).then(data => data[key]);
|
|
||||||
},
|
|
||||||
setValue(key, value) {
|
|
||||||
return wrapper.set({[key]: value});
|
|
||||||
},
|
|
||||||
getLZValue(key) {
|
|
||||||
return wrapper.getLZValues([key]).then(data => data[key]);
|
|
||||||
},
|
|
||||||
getLZValues(keys) {
|
|
||||||
return wrapper.get(keys).then((data = {}) => {
|
|
||||||
for (const key of keys) {
|
|
||||||
const value = data[key];
|
|
||||||
data[key] = value && tryJSONparse(LZString.decompressFromUTF16(value));
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setLZValue(key, value) {
|
|
||||||
return wrapper.set({[key]: LZString.compressToUTF16(JSON.stringify(value))});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return wrapper;
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
var dbExec = dbExecIndexedDB;
|
var dbExec = dbExecIndexedDB;
|
||||||
dbExec.initialized = false;
|
dbExec.initialized = false;
|
||||||
|
@ -247,6 +199,7 @@ function filterStyles({
|
||||||
matchUrl = null,
|
matchUrl = null,
|
||||||
md5Url = null,
|
md5Url = null,
|
||||||
asHash = null,
|
asHash = null,
|
||||||
|
omitCode,
|
||||||
strictRegexp = true, // used by the popup to detect bad regexps
|
strictRegexp = true, // used by the popup to detect bad regexps
|
||||||
} = {}) {
|
} = {}) {
|
||||||
enabled = enabled === null || typeof enabled === 'boolean' ? enabled :
|
enabled = enabled === null || typeof enabled === 'boolean' ? enabled :
|
||||||
|
@ -274,24 +227,34 @@ function filterStyles({
|
||||||
|
|
||||||
const cacheKey = [enabled, id, matchUrl, md5Url, asHash, strictRegexp].join('\t');
|
const cacheKey = [enabled, id, matchUrl, md5Url, asHash, strictRegexp].join('\t');
|
||||||
const cached = cachedStyles.filters.get(cacheKey);
|
const cached = cachedStyles.filters.get(cacheKey);
|
||||||
|
let styles;
|
||||||
if (cached) {
|
if (cached) {
|
||||||
cached.hits++;
|
cached.hits++;
|
||||||
cached.lastHit = Date.now();
|
cached.lastHit = Date.now();
|
||||||
return asHash
|
styles = asHash
|
||||||
? Object.assign(blankHash, cached.styles)
|
? Object.assign(blankHash, cached.styles)
|
||||||
: cached.styles;
|
: cached.styles.slice();
|
||||||
|
} else {
|
||||||
|
styles = filterStylesInternal({
|
||||||
|
enabled,
|
||||||
|
id,
|
||||||
|
matchUrl,
|
||||||
|
md5Url,
|
||||||
|
asHash,
|
||||||
|
strictRegexp,
|
||||||
|
blankHash,
|
||||||
|
cacheKey,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
if (!omitCode) return styles;
|
||||||
return filterStylesInternal({
|
if (!asHash) return styles.map(getStyleWithNoCode);
|
||||||
enabled,
|
for (const id in styles) {
|
||||||
id,
|
const style = styles[id];
|
||||||
matchUrl,
|
if (style && style.sections) {
|
||||||
md5Url,
|
styles[id] = getStyleWithNoCode(style);
|
||||||
asHash,
|
}
|
||||||
strictRegexp,
|
}
|
||||||
blankHash,
|
return styles;
|
||||||
cacheKey,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -427,6 +390,7 @@ function saveStyle(style) {
|
||||||
md5Url: null,
|
md5Url: null,
|
||||||
url: null,
|
url: null,
|
||||||
originalMd5: null,
|
originalMd5: null,
|
||||||
|
installDate: Date.now(),
|
||||||
}, style);
|
}, style);
|
||||||
return write(style);
|
return write(style);
|
||||||
}
|
}
|
||||||
|
@ -797,3 +761,47 @@ function handleCssTransitionBug({tabId, frameId, url, styles}) {
|
||||||
return RX_CSS_TRANSITION_DETECTOR.test(code.substr(Math.max(0, pos - 10), 50));
|
return RX_CSS_TRANSITION_DETECTOR.test(code.substr(Math.max(0, pos - 10), 50));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
According to CSS4 @document specification the entire URL must match.
|
||||||
|
Stylish-for-Chrome implemented it incorrectly since the very beginning.
|
||||||
|
We'll detect styles that abuse the bug by finding the sections that
|
||||||
|
would have been applied by Stylish but not by us as we follow the spec.
|
||||||
|
Additionally we'll check for invalid regexps.
|
||||||
|
*/
|
||||||
|
function detectSloppyRegexps({matchUrl, ids}) {
|
||||||
|
const results = [];
|
||||||
|
for (const id of ids) {
|
||||||
|
const style = cachedStyles.byId.get(id);
|
||||||
|
if (!style) continue;
|
||||||
|
// make sure all regexps are compiled
|
||||||
|
const rxCache = cachedStyles.regexps;
|
||||||
|
let hasRegExp = false;
|
||||||
|
for (const section of style.sections) {
|
||||||
|
for (const regexp of section.regexps) {
|
||||||
|
hasRegExp = true;
|
||||||
|
for (let pass = 1; pass <= 2; pass++) {
|
||||||
|
const cacheKey = pass === 1 ? regexp : SLOPPY_REGEXP_PREFIX + regexp;
|
||||||
|
if (!rxCache.has(cacheKey)) {
|
||||||
|
// according to CSS4 @document specification the entire URL must match
|
||||||
|
const anchored = pass === 1 ? '^(?:' + regexp + ')$' : '^' + regexp + '$';
|
||||||
|
// create in the bg context to avoid leaking of "dead objects"
|
||||||
|
const rx = tryRegExp(anchored);
|
||||||
|
rxCache.set(cacheKey, rx || false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasRegExp) continue;
|
||||||
|
const applied = getApplicableSections({style, matchUrl});
|
||||||
|
const wannabe = getApplicableSections({style, matchUrl, strictRegexp: false});
|
||||||
|
results.push({
|
||||||
|
id,
|
||||||
|
applied,
|
||||||
|
skipped: wannabe.length - applied.length,
|
||||||
|
hasInvalidRegexps: wannabe.some(({regexps}) => regexps.some(rx => !rxCache.has(rx))),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* global getStyles */
|
/* global getStyles API_METHODS */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const styleViaAPI = !CHROME && (() => {
|
API_METHODS.styleViaAPI = !CHROME && (() => {
|
||||||
const ACTIONS = {
|
const ACTIONS = {
|
||||||
styleApply,
|
styleApply,
|
||||||
styleDeleted,
|
styleDeleted,
|
||||||
|
|
|
@ -1,47 +1,74 @@
|
||||||
/* global getStyles, saveStyle, styleSectionsEqual, chromeLocal */
|
/*
|
||||||
/* global calcStyleDigest */
|
global getStyles saveStyle styleSectionsEqual
|
||||||
/* global usercss semverCompare usercssHelper */
|
global calcStyleDigest cachedStyles getStyleWithNoCode
|
||||||
|
global usercss semverCompare
|
||||||
|
global API_METHODS
|
||||||
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
var updater = {
|
var updater = (() => {
|
||||||
|
|
||||||
COUNT: 'count',
|
const STATES = {
|
||||||
UPDATED: 'updated',
|
UPDATED: 'updated',
|
||||||
SKIPPED: 'skipped',
|
SKIPPED: 'skipped',
|
||||||
DONE: 'done',
|
|
||||||
|
|
||||||
// details for SKIPPED status
|
// details for SKIPPED status
|
||||||
EDITED: 'locally edited',
|
EDITED: 'locally edited',
|
||||||
MAYBE_EDITED: 'may be locally edited',
|
MAYBE_EDITED: 'may be locally edited',
|
||||||
SAME_MD5: 'up-to-date: MD5 is unchanged',
|
SAME_MD5: 'up-to-date: MD5 is unchanged',
|
||||||
SAME_CODE: 'up-to-date: code sections are unchanged',
|
SAME_CODE: 'up-to-date: code sections are unchanged',
|
||||||
SAME_VERSION: 'up-to-date: version is unchanged',
|
SAME_VERSION: 'up-to-date: version is unchanged',
|
||||||
ERROR_MD5: 'error: MD5 is invalid',
|
ERROR_MD5: 'error: MD5 is invalid',
|
||||||
ERROR_JSON: 'error: JSON is invalid',
|
ERROR_JSON: 'error: JSON is invalid',
|
||||||
ERROR_VERSION: 'error: version is older than installed style',
|
ERROR_VERSION: 'error: version is older than installed style',
|
||||||
|
};
|
||||||
|
|
||||||
lastUpdateTime: parseInt(localStorage.lastUpdateTime) || Date.now(),
|
let lastUpdateTime = parseInt(localStorage.lastUpdateTime) || Date.now();
|
||||||
|
let checkingAll = false;
|
||||||
|
let logQueue = [];
|
||||||
|
let logLastWriteTime = 0;
|
||||||
|
|
||||||
checkAllStyles({observer = () => {}, save = true, ignoreDigest} = {}) {
|
API_METHODS.updateCheckAll = checkAllStyles;
|
||||||
updater.resetInterval();
|
API_METHODS.updateCheck = checkStyle;
|
||||||
updater.checkAllStyles.running = true;
|
API_METHODS.getUpdaterStates = () => updater.STATES;
|
||||||
|
|
||||||
|
prefs.subscribe(['updateInterval'], schedule);
|
||||||
|
schedule();
|
||||||
|
|
||||||
|
return {checkAllStyles, checkStyle, STATES};
|
||||||
|
|
||||||
|
function checkAllStyles({
|
||||||
|
save = true,
|
||||||
|
ignoreDigest,
|
||||||
|
observe,
|
||||||
|
} = {}) {
|
||||||
|
resetInterval();
|
||||||
|
checkingAll = true;
|
||||||
|
const port = observe && chrome.runtime.connect({name: 'updater'});
|
||||||
return getStyles({}).then(styles => {
|
return getStyles({}).then(styles => {
|
||||||
styles = styles.filter(style => style.updateUrl);
|
styles = styles.filter(style => style.updateUrl);
|
||||||
observer(updater.COUNT, styles.length);
|
if (port) port.postMessage({count: styles.length});
|
||||||
updater.log('');
|
log('');
|
||||||
updater.log(`${save ? 'Scheduled' : 'Manual'} update check for ${styles.length} styles`);
|
log(`${save ? 'Scheduled' : 'Manual'} update check for ${styles.length} styles`);
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
styles.map(style =>
|
styles.map(style =>
|
||||||
updater.checkStyle({style, observer, save, ignoreDigest})));
|
checkStyle({style, port, save, ignoreDigest})));
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
observer(updater.DONE);
|
if (port) port.postMessage({done: true});
|
||||||
updater.log('');
|
if (port) port.disconnect();
|
||||||
updater.checkAllStyles.running = false;
|
log('');
|
||||||
|
checkingAll = false;
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
checkStyle({style, observer = () => {}, save = true, ignoreDigest}) {
|
function checkStyle({
|
||||||
|
id,
|
||||||
|
style = cachedStyles.byId.get(id),
|
||||||
|
port,
|
||||||
|
save = true,
|
||||||
|
ignoreDigest,
|
||||||
|
}) {
|
||||||
/*
|
/*
|
||||||
Original style digests are calculated in these cases:
|
Original style digests are calculated in these cases:
|
||||||
* style is installed or updated from server
|
* style is installed or updated from server
|
||||||
|
@ -65,29 +92,33 @@ var updater = {
|
||||||
.catch(reportFailure);
|
.catch(reportFailure);
|
||||||
|
|
||||||
function reportSuccess(saved) {
|
function reportSuccess(saved) {
|
||||||
observer(updater.UPDATED, saved);
|
log(STATES.UPDATED + ` #${style.id} ${style.name}`);
|
||||||
updater.log(updater.UPDATED + ` #${style.id} ${style.name}`);
|
const info = {updated: true, style: saved};
|
||||||
|
if (port) port.postMessage(info);
|
||||||
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reportFailure(err) {
|
function reportFailure(error) {
|
||||||
observer(updater.SKIPPED, style, err);
|
error = error === 0 ? 'server unreachable' : error;
|
||||||
err = err === 0 ? 'server unreachable' : err;
|
log(STATES.SKIPPED + ` (${error}) #${style.id} ${style.name}`);
|
||||||
updater.log(updater.SKIPPED + ` (${err}) #${style.id} ${style.name}`);
|
const info = {error, STATES, style: getStyleWithNoCode(style)};
|
||||||
|
if (port) port.postMessage(info);
|
||||||
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkIfEdited(digest) {
|
function checkIfEdited(digest) {
|
||||||
if (style.originalDigest && style.originalDigest !== digest) {
|
if (style.originalDigest && style.originalDigest !== digest) {
|
||||||
return Promise.reject(updater.EDITED);
|
return Promise.reject(STATES.EDITED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeUpdateUSO() {
|
function maybeUpdateUSO() {
|
||||||
return download(style.md5Url).then(md5 => {
|
return download(style.md5Url).then(md5 => {
|
||||||
if (!md5 || md5.length !== 32) {
|
if (!md5 || md5.length !== 32) {
|
||||||
return Promise.reject(updater.ERROR_MD5);
|
return Promise.reject(STATES.ERROR_MD5);
|
||||||
}
|
}
|
||||||
if (md5 === style.originalMd5 && style.originalDigest && !ignoreDigest) {
|
if (md5 === style.originalMd5 && style.originalDigest && !ignoreDigest) {
|
||||||
return Promise.reject(updater.SAME_MD5);
|
return Promise.reject(STATES.SAME_MD5);
|
||||||
}
|
}
|
||||||
return download(style.updateUrl)
|
return download(style.updateUrl)
|
||||||
.then(text => tryJSONparse(text));
|
.then(text => tryJSONparse(text));
|
||||||
|
@ -104,14 +135,14 @@ var updater = {
|
||||||
case 0:
|
case 0:
|
||||||
// re-install is invalid in a soft upgrade
|
// re-install is invalid in a soft upgrade
|
||||||
if (!ignoreDigest) {
|
if (!ignoreDigest) {
|
||||||
return Promise.reject(updater.SAME_VERSION);
|
return Promise.reject(STATES.SAME_VERSION);
|
||||||
} else if (text === style.sourceCode) {
|
} else if (text === style.sourceCode) {
|
||||||
return Promise.reject(updater.SAME_CODE);
|
return Promise.reject(STATES.SAME_CODE);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
// downgrade is always invalid
|
// downgrade is always invalid
|
||||||
return Promise.reject(updater.ERROR_VERSION);
|
return Promise.reject(STATES.ERROR_VERSION);
|
||||||
}
|
}
|
||||||
return usercss.buildCode(json);
|
return usercss.buildCode(json);
|
||||||
});
|
});
|
||||||
|
@ -120,8 +151,9 @@ var updater = {
|
||||||
function maybeSave(json = {}) {
|
function maybeSave(json = {}) {
|
||||||
// usercss is already validated while building
|
// usercss is already validated while building
|
||||||
if (!json.usercssData && !styleJSONseemsValid(json)) {
|
if (!json.usercssData && !styleJSONseemsValid(json)) {
|
||||||
return Promise.reject(updater.ERROR_JSON);
|
return Promise.reject(STATES.ERROR_JSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
json.id = style.id;
|
json.id = style.id;
|
||||||
json.updateDate = Date.now();
|
json.updateDate = Date.now();
|
||||||
json.reason = 'update';
|
json.reason = 'update';
|
||||||
|
@ -139,15 +171,16 @@ var updater = {
|
||||||
if (styleSectionsEqual(json, style)) {
|
if (styleSectionsEqual(json, style)) {
|
||||||
// update digest even if save === false as there might be just a space added etc.
|
// update digest even if save === false as there might be just a space added etc.
|
||||||
saveStyle(Object.assign(json, {reason: 'update-digest'}));
|
saveStyle(Object.assign(json, {reason: 'update-digest'}));
|
||||||
return Promise.reject(updater.SAME_CODE);
|
return Promise.reject(STATES.SAME_CODE);
|
||||||
} else if (!style.originalDigest && !ignoreDigest) {
|
|
||||||
return Promise.reject(updater.MAYBE_EDITED);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return !save ? json :
|
if (!style.originalDigest && !ignoreDigest) {
|
||||||
json.usercssData
|
return Promise.reject(STATES.MAYBE_EDITED);
|
||||||
? usercssHelper.save(json)
|
}
|
||||||
: saveStyle(json);
|
|
||||||
|
return save ?
|
||||||
|
API_METHODS[json.usercssData ? 'saveUsercss' : 'saveStyle'](json) :
|
||||||
|
json;
|
||||||
}
|
}
|
||||||
|
|
||||||
function styleJSONseemsValid(json) {
|
function styleJSONseemsValid(json) {
|
||||||
|
@ -157,49 +190,47 @@ var updater = {
|
||||||
&& typeof json.sections.every === 'function'
|
&& typeof json.sections.every === 'function'
|
||||||
&& typeof json.sections[0].code === 'string';
|
&& typeof json.sections[0].code === 'string';
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
schedule() {
|
function schedule() {
|
||||||
const interval = prefs.get('updateInterval') * 60 * 60 * 1000;
|
const interval = prefs.get('updateInterval') * 60 * 60 * 1000;
|
||||||
if (interval) {
|
if (interval) {
|
||||||
const elapsed = Math.max(0, Date.now() - updater.lastUpdateTime);
|
const elapsed = Math.max(0, Date.now() - lastUpdateTime);
|
||||||
debounce(updater.checkAllStyles, Math.max(10e3, interval - elapsed));
|
debounce(checkAllStyles, Math.max(10e3, interval - elapsed));
|
||||||
} else {
|
} else {
|
||||||
debounce.unregister(updater.checkAllStyles);
|
debounce.unregister(checkAllStyles);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
resetInterval() {
|
function resetInterval() {
|
||||||
localStorage.lastUpdateTime = updater.lastUpdateTime = Date.now();
|
localStorage.lastUpdateTime = lastUpdateTime = Date.now();
|
||||||
updater.schedule();
|
schedule();
|
||||||
},
|
}
|
||||||
|
|
||||||
log: (() => {
|
function log(text) {
|
||||||
let queue = [];
|
logQueue.push({text, time: new Date().toLocaleString()});
|
||||||
let lastWriteTime = 0;
|
debounce(flushQueue, text && checkingAll ? 1000 : 0);
|
||||||
return text => {
|
}
|
||||||
queue.push({text, time: new Date().toLocaleString()});
|
|
||||||
debounce(flushQueue, text && updater.checkAllStyles.running ? 1000 : 0);
|
function flushQueue(stored) {
|
||||||
};
|
if (!stored) {
|
||||||
function flushQueue() {
|
chrome.storage.local.get('updateLog', flushQueue);
|
||||||
chromeLocal.getValue('updateLog').then((lines = []) => {
|
return;
|
||||||
const time = Date.now() - lastWriteTime > 11e3 ? queue[0].time + ' ' : '';
|
|
||||||
if (!queue[0].text) {
|
|
||||||
queue.shift();
|
|
||||||
if (lines[lines.length - 1]) {
|
|
||||||
lines.push('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lines.splice(0, lines.length - 1000);
|
|
||||||
lines.push(time + queue[0].text);
|
|
||||||
lines.push(...queue.slice(1).map(item => item.text));
|
|
||||||
chromeLocal.setValue('updateLog', lines);
|
|
||||||
lastWriteTime = Date.now();
|
|
||||||
queue = [];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
})(),
|
const lines = stored.lines || [];
|
||||||
};
|
const time = Date.now() - logLastWriteTime > 11e3 ?
|
||||||
|
logQueue[0].time + ' ' :
|
||||||
|
'';
|
||||||
|
if (!logQueue[0].text) {
|
||||||
|
logQueue.shift();
|
||||||
|
if (lines[lines.length - 1]) lines.push('');
|
||||||
|
}
|
||||||
|
lines.splice(0, lines.length - 1000);
|
||||||
|
lines.push(time + (logQueue[0] && logQueue[0].text || ''));
|
||||||
|
lines.push(...logQueue.slice(1).map(item => item.text));
|
||||||
|
|
||||||
updater.schedule();
|
chrome.storage.local.set({updateLog: lines});
|
||||||
prefs.subscribe(['updateInterval'], updater.schedule);
|
logLastWriteTime = Date.now();
|
||||||
|
logQueue = [];
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
/* global usercss saveStyle getStyles chromeLocal */
|
/* global API_METHODS usercss saveStyle getStyles chromeLocal cachedStyles */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
(() => {
|
||||||
var usercssHelper = (() => {
|
|
||||||
|
API_METHODS.saveUsercss = save;
|
||||||
|
API_METHODS.buildUsercss = build;
|
||||||
|
API_METHODS.installUsercss = install;
|
||||||
|
|
||||||
const TEMP_CODE_PREFIX = 'tempUsercssCode';
|
const TEMP_CODE_PREFIX = 'tempUsercssCode';
|
||||||
const TEMP_CODE_CLEANUP_DELAY = 60e3;
|
const TEMP_CODE_CLEANUP_DELAY = 60e3;
|
||||||
|
@ -48,31 +51,25 @@ var usercssHelper = (() => {
|
||||||
return usercss.buildCode(style);
|
return usercss.buildCode(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapReject(pending) {
|
|
||||||
return pending
|
|
||||||
.catch(err => new Error(Array.isArray(err) ? err.join('\n') : err.message || String(err)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the source and find the duplication
|
// Parse the source and find the duplication
|
||||||
function build({sourceCode, checkDup = false}, noReject) {
|
function build({sourceCode, checkDup = false}) {
|
||||||
const pending = buildMeta({sourceCode})
|
return buildMeta({sourceCode})
|
||||||
.then(style => Promise.all([
|
.then(style => Promise.all([
|
||||||
buildCode(style),
|
buildCode(style),
|
||||||
checkDup && findDup(style)
|
checkDup && findDup(style)
|
||||||
]))
|
]))
|
||||||
.then(([style, dup]) => ({style, dup}));
|
.then(([style, dup]) => ({style, dup}));
|
||||||
|
|
||||||
return noReject ? wrapReject(pending) : pending;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function save(style, noReject) {
|
function save(style) {
|
||||||
const pending = buildMeta(style)
|
if (!style.sourceCode) {
|
||||||
|
style.sourceCode = cachedStyles.byId.get(style.id).sourceCode;
|
||||||
|
}
|
||||||
|
return buildMeta(style)
|
||||||
.then(assignVars)
|
.then(assignVars)
|
||||||
.then(buildCode)
|
.then(buildCode)
|
||||||
.then(saveStyle);
|
.then(saveStyle);
|
||||||
|
|
||||||
return noReject ? wrapReject(pending) : pending;
|
|
||||||
|
|
||||||
function assignVars(style) {
|
function assignVars(style) {
|
||||||
if (style.reason === 'config' && style.id) {
|
if (style.reason === 'config' && style.id) {
|
||||||
return style;
|
return style;
|
||||||
|
@ -105,11 +102,12 @@ var usercssHelper = (() => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function openInstallPage(tab, {url = tab.url, direct, downloaded} = {}) {
|
function install({url, direct, downloaded}, {tab}) {
|
||||||
|
url = url || tab.url;
|
||||||
if (direct && !downloaded) {
|
if (direct && !downloaded) {
|
||||||
prefetchCodeForInstallation(tab.id, url);
|
prefetchCodeForInstallation(tab.id, url);
|
||||||
}
|
}
|
||||||
return wrapReject(openURL({
|
return openURL({
|
||||||
url: '/install-usercss.html' +
|
url: '/install-usercss.html' +
|
||||||
'?updateUrl=' + encodeURIComponent(url) +
|
'?updateUrl=' + encodeURIComponent(url) +
|
||||||
'&tabId=' + tab.id +
|
'&tabId=' + tab.id +
|
||||||
|
@ -117,7 +115,7 @@ var usercssHelper = (() => {
|
||||||
index: tab.index + 1,
|
index: tab.index + 1,
|
||||||
openerTabId: tab.id,
|
openerTabId: tab.id,
|
||||||
currentWindow: null,
|
currentWindow: null,
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function prefetchCodeForInstallation(tabId, url) {
|
function prefetchCodeForInstallation(tabId, url) {
|
||||||
|
@ -131,6 +129,4 @@ var usercssHelper = (() => {
|
||||||
setTimeout(() => chromeLocal.remove(key), TEMP_CODE_CLEANUP_DELAY);
|
setTimeout(() => chromeLocal.remove(key), TEMP_CODE_CLEANUP_DELAY);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {build, save, findDup, openInstallPage};
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -48,8 +48,8 @@
|
||||||
asHash: true,
|
asHash: true,
|
||||||
}, options);
|
}, options);
|
||||||
// On own pages we request the styles directly to minimize delay and flicker
|
// On own pages we request the styles directly to minimize delay and flicker
|
||||||
if (typeof getStylesSafe === 'function') {
|
if (typeof API === 'function') {
|
||||||
getStylesSafe(request).then(callback);
|
API.getStyles(request).then(callback);
|
||||||
} else {
|
} else {
|
||||||
chrome.runtime.sendMessage(request, callback);
|
chrome.runtime.sendMessage(request, callback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ function initUsercssInstall() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
chrome.runtime.sendMessage({
|
chrome.runtime.sendMessage({
|
||||||
method: 'openUsercssInstallPage',
|
method: 'installUsercss',
|
||||||
url: location.href,
|
url: location.href,
|
||||||
}, r => r && r.__ERROR__ && alert(r.__ERROR__));
|
}, r => r && r.__ERROR__ && alert(r.__ERROR__));
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,7 +198,14 @@
|
||||||
if (url.startsWith('#')) {
|
if (url.startsWith('#')) {
|
||||||
resolve(document.getElementById(url.slice(1)).textContent);
|
resolve(document.getElementById(url.slice(1)).textContent);
|
||||||
} else {
|
} else {
|
||||||
chrome.runtime.sendMessage({method: 'download', url}, resolve);
|
chrome.runtime.sendMessage({method: 'download', url}, result => {
|
||||||
|
const error = result && result.__ERROR__;
|
||||||
|
if (error) {
|
||||||
|
alert('Error' + (error ? '\n' + error : ''));
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
<script src="js/localization.js"></script>
|
<script src="js/localization.js"></script>
|
||||||
<script src="js/script-loader.js"></script>
|
<script src="js/script-loader.js"></script>
|
||||||
<script src="js/moz-parser.js"></script>
|
<script src="js/moz-parser.js"></script>
|
||||||
|
<script src="js/storage-util.js"></script>
|
||||||
<script src="content/apply.js"></script>
|
<script src="content/apply.js"></script>
|
||||||
<script src="edit/lint.js"></script>
|
<script src="edit/lint.js"></script>
|
||||||
<script src="edit/util.js"></script>
|
<script src="edit/util.js"></script>
|
||||||
|
|
25
edit/edit.js
25
edit/edit.js
|
@ -44,7 +44,7 @@ Promise.all([
|
||||||
if (usercss) {
|
if (usercss) {
|
||||||
editor = createSourceEditor(style);
|
editor = createSourceEditor(style);
|
||||||
} else {
|
} else {
|
||||||
initWithSectionStyle({style});
|
initWithSectionStyle(style);
|
||||||
document.addEventListener('wheel', scrollEntirePageOnCtrlShift);
|
document.addEventListener('wheel', scrollEntirePageOnCtrlShift);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -155,14 +155,17 @@ function onRuntimeMessage(request) {
|
||||||
request.reason !== 'editSave' &&
|
request.reason !== 'editSave' &&
|
||||||
request.reason !== 'config') {
|
request.reason !== 'config') {
|
||||||
// code-less style from notifyAllTabs
|
// code-less style from notifyAllTabs
|
||||||
if ((request.style.sections[0] || {}).code === null) {
|
const {sections, id} = request.style;
|
||||||
request.style = BG.cachedStyles.byId.get(request.style.id);
|
((sections[0] || {}).code === null
|
||||||
}
|
? API.getStyles({id})
|
||||||
if (isUsercss(request.style)) {
|
: Promise.resolve([request.style])
|
||||||
editor.replaceStyle(request.style, request.codeIsUpdated);
|
).then(([style]) => {
|
||||||
} else {
|
if (isUsercss(style)) {
|
||||||
initWithSectionStyle(request);
|
editor.replaceStyle(style, request.codeIsUpdated);
|
||||||
}
|
} else {
|
||||||
|
initWithSectionStyle(style, request.codeIsUpdated);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'styleDeleted':
|
case 'styleDeleted':
|
||||||
|
@ -228,7 +231,7 @@ function initStyleData() {
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
return getStylesSafe({id: id || -1})
|
return API.getStyles({id: id || -1})
|
||||||
.then(([style = createEmptyStyle()]) => {
|
.then(([style = createEmptyStyle()]) => {
|
||||||
styleId = style.id;
|
styleId = style.id;
|
||||||
if (styleId) sessionStorage.justEditedStyleId = styleId;
|
if (styleId) sessionStorage.justEditedStyleId = styleId;
|
||||||
|
@ -344,7 +347,7 @@ function save() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveStyleSafe({
|
API.saveStyle({
|
||||||
id: styleId,
|
id: styleId,
|
||||||
name: $('#name').value.trim(),
|
name: $('#name').value.trim(),
|
||||||
enabled: $('#enabled').checked,
|
enabled: $('#enabled').checked,
|
||||||
|
|
10
edit/lint.js
10
edit/lint.js
|
@ -121,12 +121,12 @@ var linterConfig = {
|
||||||
config = this.fallbackToDefaults(config);
|
config = this.fallbackToDefaults(config);
|
||||||
const linter = linterConfig.getName();
|
const linter = linterConfig.getName();
|
||||||
this[linter] = config;
|
this[linter] = config;
|
||||||
BG.chromeSync.setLZValue(this.storageName[linter], config);
|
chromeSync.setLZValue(this.storageName[linter], config);
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
|
|
||||||
loadAll() {
|
loadAll() {
|
||||||
return BG.chromeSync.getLZValues([
|
return chromeSync.getLZValues([
|
||||||
'editorCSSLintConfig',
|
'editorCSSLintConfig',
|
||||||
'editorStylelintConfig',
|
'editorStylelintConfig',
|
||||||
]).then(data => {
|
]).then(data => {
|
||||||
|
@ -167,10 +167,8 @@ var linterConfig = {
|
||||||
},
|
},
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
if (!linterConfig.init.pending) {
|
if (!this.init.pending) this.init.pending = this.loadAll();
|
||||||
linterConfig.init.pending = linterConfig.loadAll();
|
return this.init.pending;
|
||||||
}
|
|
||||||
return linterConfig.init.pending;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ global showAppliesToHelp beautify regExpTester setGlobalProgress setCleanSection
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function initWithSectionStyle({style, codeIsUpdated}) {
|
function initWithSectionStyle(style, codeIsUpdated) {
|
||||||
$('#name').value = style.name || '';
|
$('#name').value = style.name || '';
|
||||||
$('#enabled').checked = style.enabled !== false;
|
$('#enabled').checked = style.enabled !== false;
|
||||||
$('#url').href = style.url || '';
|
$('#url').href = style.url || '';
|
||||||
|
|
|
@ -106,7 +106,7 @@ function createSourceEditor(style) {
|
||||||
`.replace(/^\s+/gm, '');
|
`.replace(/^\s+/gm, '');
|
||||||
dirty.clear('sourceGeneration');
|
dirty.clear('sourceGeneration');
|
||||||
style.sourceCode = '';
|
style.sourceCode = '';
|
||||||
BG.chromeSync.getLZValue('usercssTemplate').then(code => {
|
chromeSync.getLZValue('usercssTemplate').then(code => {
|
||||||
style.sourceCode = code || DEFAULT_CODE;
|
style.sourceCode = code || DEFAULT_CODE;
|
||||||
cm.startOperation();
|
cm.startOperation();
|
||||||
cm.setValue(style.sourceCode);
|
cm.setValue(style.sourceCode);
|
||||||
|
@ -216,8 +216,8 @@ function createSourceEditor(style) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const code = cm.getValue();
|
const code = cm.getValue();
|
||||||
return onBackgroundReady()
|
return (
|
||||||
.then(() => BG.usercssHelper.save({
|
API.saveUsercss({
|
||||||
reason: 'editSave',
|
reason: 'editSave',
|
||||||
id: style.id,
|
id: style.id,
|
||||||
enabled: style.enabled,
|
enabled: style.enabled,
|
||||||
|
@ -228,8 +228,8 @@ function createSourceEditor(style) {
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (err.message === t('styleMissingMeta', 'name')) {
|
if (err.message === t('styleMissingMeta', 'name')) {
|
||||||
messageBox.confirm(t('usercssReplaceTemplateConfirmation')).then(ok => ok &&
|
messageBox.confirm(t('usercssReplaceTemplateConfirmation')).then(ok => ok &&
|
||||||
BG.chromeSync.setLZValue('usercssTemplate', code)
|
chromeSync.setLZValue('usercssTemplate', code)
|
||||||
.then(() => BG.chromeSync.getLZValue('usercssTemplate'))
|
.then(() => chromeSync.getLZValue('usercssTemplate'))
|
||||||
.then(saved => saved !== code && messageBox.alert(t('syncStorageErrorSaving'))));
|
.then(saved => saved !== code && messageBox.alert(t('syncStorageErrorSaving'))));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,7 +200,7 @@
|
||||||
if (!liveReload && !prefs.get('openEditInWindow')) {
|
if (!liveReload && !prefs.get('openEditInWindow')) {
|
||||||
chrome.tabs.update({url: '/edit.html?id=' + style.id});
|
chrome.tabs.update({url: '/edit.html?id=' + style.id});
|
||||||
} else {
|
} else {
|
||||||
BG.openEditor(style.id);
|
API.openEditor({id: style.id});
|
||||||
if (!liveReload) {
|
if (!liveReload) {
|
||||||
closeCurrentTab();
|
closeCurrentTab();
|
||||||
}
|
}
|
||||||
|
@ -212,8 +212,8 @@
|
||||||
function initSourceCode(sourceCode) {
|
function initSourceCode(sourceCode) {
|
||||||
cm.setValue(sourceCode);
|
cm.setValue(sourceCode);
|
||||||
cm.refresh();
|
cm.refresh();
|
||||||
BG.usercssHelper.build(BG.deepCopy({sourceCode, checkDup: true}))
|
API.buildUsercss({sourceCode, checkDup: true})
|
||||||
.then(r => init(deepCopy(r)))
|
.then(r => init(r instanceof Object ? r : deepCopy(r)))
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
$('.header').classList.add('meta-init-error');
|
$('.header').classList.add('meta-init-error');
|
||||||
showError(err);
|
showError(err);
|
||||||
|
@ -222,7 +222,7 @@
|
||||||
|
|
||||||
function buildWarning(err) {
|
function buildWarning(err) {
|
||||||
const contents = Array.isArray(err) ?
|
const contents = Array.isArray(err) ?
|
||||||
$create('pre', err.join('\n')) :
|
[$create('pre', err.join('\n'))] :
|
||||||
[err && err.message || err || 'Unknown error'];
|
[err && err.message || err || 'Unknown error'];
|
||||||
if (Number.isInteger(err.index)) {
|
if (Number.isInteger(err.index)) {
|
||||||
const pos = cm.posFromIndex(err.index);
|
const pos = cm.posFromIndex(err.index);
|
||||||
|
@ -283,8 +283,8 @@
|
||||||
data.version,
|
data.version,
|
||||||
]))
|
]))
|
||||||
).then(ok => ok &&
|
).then(ok => ok &&
|
||||||
BG.usercssHelper.save(BG.deepCopy(Object.assign(style, dup && {reason: 'update'})))
|
API.saveUsercss(Object.assign(style, dup && {reason: 'update'}))
|
||||||
.then(r => install(deepCopy(r)))
|
.then(r => install(r instanceof Object ? r : deepCopy(r)))
|
||||||
.catch(err => messageBox.alert(t('styleInstallFailed', err)))
|
.catch(err => messageBox.alert(t('styleInstallFailed', err)))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -64,7 +64,8 @@ onDOMready().then(() => {
|
||||||
if (!chrome.app && chrome.windows) {
|
if (!chrome.app && chrome.windows) {
|
||||||
// die if unable to access BG directly
|
// die if unable to access BG directly
|
||||||
chrome.windows.getCurrent(wnd => {
|
chrome.windows.getCurrent(wnd => {
|
||||||
if (!BG && wnd.incognito) {
|
if (!BG && wnd.incognito &&
|
||||||
|
!location.pathname.includes('popup.html')) {
|
||||||
// private windows can't get bg page
|
// private windows can't get bg page
|
||||||
location.href = '/msgbox/dysfunctional.html';
|
location.href = '/msgbox/dysfunctional.html';
|
||||||
throw 0;
|
throw 0;
|
||||||
|
|
181
js/messaging.js
181
js/messaging.js
|
@ -1,14 +1,18 @@
|
||||||
/* global BG: true, onRuntimeMessage, applyOnMessage, handleUpdate, handleDelete */
|
/*
|
||||||
/* global FIREFOX: true */
|
global BG: true
|
||||||
|
global FIREFOX: true
|
||||||
|
global onRuntimeMessage applyOnMessage
|
||||||
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// keep message channel open for sendResponse in chrome.runtime.onMessage listener
|
// keep message channel open for sendResponse in chrome.runtime.onMessage listener
|
||||||
const KEEP_CHANNEL_OPEN = true;
|
const KEEP_CHANNEL_OPEN = true;
|
||||||
|
|
||||||
const CHROME = Boolean(chrome.app) && parseInt(navigator.userAgent.match(/Chrom\w+\/(?:\d+\.){2}(\d+)|$/)[1]);
|
const CHROME = Boolean(chrome.app) && parseInt(navigator.userAgent.match(/Chrom\w+\/(?:\d+\.){2}(\d+)|$/)[1]);
|
||||||
const OPERA = CHROME && parseFloat(navigator.userAgent.match(/\bOPR\/(\d+\.\d+)|$/)[1]);
|
const OPERA = Boolean(chrome.app) && parseFloat(navigator.userAgent.match(/\bOPR\/(\d+\.\d+)|$/)[1]);
|
||||||
|
const VIVALDI = Boolean(chrome.app) && navigator.userAgent.includes('Vivaldi');
|
||||||
const ANDROID = !chrome.windows;
|
const ANDROID = !chrome.windows;
|
||||||
let FIREFOX = !CHROME && parseFloat(navigator.userAgent.match(/\bFirefox\/(\d+\.\d+)|$/)[1]);
|
let FIREFOX = !chrome.app && parseFloat(navigator.userAgent.match(/\bFirefox\/(\d+\.\d+)|$/)[1]);
|
||||||
|
|
||||||
if (!CHROME && !chrome.browserAction.openPopup) {
|
if (!CHROME && !chrome.browserAction.openPopup) {
|
||||||
// in FF pre-57 legacy addons can override useragent so we assume the worst
|
// in FF pre-57 legacy addons can override useragent so we assume the worst
|
||||||
|
@ -65,13 +69,14 @@ if (!BG || BG !== window) {
|
||||||
document.documentElement.classList.add('firefox');
|
document.documentElement.classList.add('firefox');
|
||||||
} else if (OPERA) {
|
} else if (OPERA) {
|
||||||
document.documentElement.classList.add('opera');
|
document.documentElement.classList.add('opera');
|
||||||
} else if (chrome.app && navigator.userAgent.includes('Vivaldi')) {
|
} else {
|
||||||
document.documentElement.classList.add('vivaldi');
|
if (VIVALDI) document.documentElement.classList.add('vivaldi');
|
||||||
}
|
}
|
||||||
// TODO: remove once our manifest's minimum_chrome_version is 50+
|
// TODO: remove once our manifest's minimum_chrome_version is 50+
|
||||||
// Chrome 49 doesn't report own extension pages in webNavigation apparently
|
// Chrome 49 doesn't report own extension pages in webNavigation apparently
|
||||||
if (CHROME && CHROME < 2661) {
|
if (CHROME && CHROME < 2661) {
|
||||||
getActiveTab().then(BG.updateIcon);
|
getActiveTab().then(tab =>
|
||||||
|
window.API.updateIcon({tab}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +87,60 @@ if (FIREFOX_NO_DOM_STORAGE) {
|
||||||
Object.defineProperty(window, 'sessionStorage', {value: {}});
|
Object.defineProperty(window, 'sessionStorage', {value: {}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var API = (() => {
|
||||||
|
return new Proxy(() => {}, {
|
||||||
|
get: (target, name) =>
|
||||||
|
name === 'remoteCall' ?
|
||||||
|
remoteCall :
|
||||||
|
arg => invokeBG(name, arg),
|
||||||
|
});
|
||||||
|
|
||||||
|
function remoteCall(name, arg, remoteWindow) {
|
||||||
|
let thing = window[name] || window.API_METHODS[name];
|
||||||
|
if (typeof thing === 'function') {
|
||||||
|
thing = thing(arg);
|
||||||
|
}
|
||||||
|
if (!thing || typeof thing !== 'object') {
|
||||||
|
return thing;
|
||||||
|
} else if (thing instanceof Promise) {
|
||||||
|
return thing.then(product => remoteWindow.deepCopy(product));
|
||||||
|
} else {
|
||||||
|
return remoteWindow.deepCopy(thing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function invokeBG(name, arg = {}) {
|
||||||
|
if (BG && (name in BG || name in BG.API_METHODS)) {
|
||||||
|
const call = BG !== window ?
|
||||||
|
BG.API.remoteCall(name, BG.deepCopy(arg), window) :
|
||||||
|
remoteCall(name, arg, BG);
|
||||||
|
return Promise.resolve(call);
|
||||||
|
}
|
||||||
|
if (BG && BG.getStyles) {
|
||||||
|
throw new Error('Bad API method', name, arg);
|
||||||
|
}
|
||||||
|
if (FIREFOX) {
|
||||||
|
arg.method = name;
|
||||||
|
return sendMessage(arg);
|
||||||
|
}
|
||||||
|
return onBackgroundReady().then(() => invokeBG(name, arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBackgroundReady() {
|
||||||
|
return BG && BG.getStyles ? Promise.resolve() : new Promise(function ping(resolve) {
|
||||||
|
sendMessage({method: 'healthCheck'}, health => {
|
||||||
|
if (health !== undefined) {
|
||||||
|
BG = chrome.extension.getBackgroundPage();
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
setTimeout(ping, 0, resolve);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
function notifyAllTabs(msg) {
|
function notifyAllTabs(msg) {
|
||||||
const originalMessage = msg;
|
const originalMessage = msg;
|
||||||
|
@ -99,6 +158,12 @@ function notifyAllTabs(msg) {
|
||||||
const affectsIcon = affectsAll || msg.affects.icon;
|
const affectsIcon = affectsAll || msg.affects.icon;
|
||||||
const affectsPopup = affectsAll || msg.affects.popup;
|
const affectsPopup = affectsAll || msg.affects.popup;
|
||||||
const affectsSelf = affectsPopup || msg.prefs;
|
const affectsSelf = affectsPopup || msg.prefs;
|
||||||
|
// notify background page and all open popups
|
||||||
|
if (affectsSelf) {
|
||||||
|
msg.tabId = undefined;
|
||||||
|
sendMessage(msg, ignoreChromeError);
|
||||||
|
}
|
||||||
|
// notify tabs
|
||||||
if (affectsTabs || affectsIcon) {
|
if (affectsTabs || affectsIcon) {
|
||||||
const notifyTab = tab => {
|
const notifyTab = tab => {
|
||||||
// own pages will be notified via runtime.sendMessage later
|
// own pages will be notified via runtime.sendMessage later
|
||||||
|
@ -109,8 +174,9 @@ function notifyAllTabs(msg) {
|
||||||
msg.tabId = tab.id;
|
msg.tabId = tab.id;
|
||||||
sendMessage(msg, ignoreChromeError);
|
sendMessage(msg, ignoreChromeError);
|
||||||
}
|
}
|
||||||
if (affectsIcon && BG) {
|
if (affectsIcon) {
|
||||||
BG.updateIcon(tab);
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
debounce(API.updateIcon, 0, {tab});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// list all tabs including chrome-extension:// which can be ours
|
// list all tabs including chrome-extension:// which can be ours
|
||||||
|
@ -132,11 +198,6 @@ function notifyAllTabs(msg) {
|
||||||
if (typeof applyOnMessage !== 'undefined') {
|
if (typeof applyOnMessage !== 'undefined') {
|
||||||
applyOnMessage(originalMessage);
|
applyOnMessage(originalMessage);
|
||||||
}
|
}
|
||||||
// notify background page and all open popups
|
|
||||||
if (affectsSelf) {
|
|
||||||
msg.tabId = undefined;
|
|
||||||
sendMessage(msg, ignoreChromeError);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -294,10 +355,9 @@ function ignoreChromeError() {
|
||||||
|
|
||||||
|
|
||||||
function getStyleWithNoCode(style) {
|
function getStyleWithNoCode(style) {
|
||||||
const stripped = Object.assign({}, style, {sections: []});
|
const stripped = deepCopy(style);
|
||||||
for (const section of style.sections) {
|
for (const section of stripped.sections) section.code = null;
|
||||||
stripped.sections.push(Object.assign({}, section, {code: null}));
|
stripped.sourceCode = null;
|
||||||
}
|
|
||||||
return stripped;
|
return stripped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,31 +403,23 @@ const debounce = Object.assign((fn, delay, ...args) => {
|
||||||
|
|
||||||
|
|
||||||
function deepCopy(obj) {
|
function deepCopy(obj) {
|
||||||
return obj !== null && obj !== undefined && typeof obj === 'object'
|
if (!obj || typeof obj !== 'object') return obj;
|
||||||
? deepMerge(typeof obj.slice === 'function' ? [] : {}, obj)
|
// N.B. a copy should be an explicitly literal
|
||||||
: obj;
|
if (Array.isArray(obj)) {
|
||||||
}
|
const copy = [];
|
||||||
|
for (const v of obj) {
|
||||||
|
copy.push(!v || typeof v !== 'object' ? v : deepCopy(v));
|
||||||
function deepMerge(target, ...args) {
|
|
||||||
const isArray = typeof target.slice === 'function';
|
|
||||||
for (const obj of args) {
|
|
||||||
if (isArray && obj !== null && obj !== undefined) {
|
|
||||||
for (const element of obj) {
|
|
||||||
target.push(deepCopy(element));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (const k in obj) {
|
|
||||||
const value = obj[k];
|
|
||||||
if (k in target && typeof value === 'object' && value !== null) {
|
|
||||||
deepMerge(target[k], value);
|
|
||||||
} else {
|
|
||||||
target[k] = deepCopy(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return copy;
|
||||||
}
|
}
|
||||||
return target;
|
const copy = {};
|
||||||
|
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||||
|
for (const k in obj) {
|
||||||
|
if (!hasOwnProperty.call(obj, k)) continue;
|
||||||
|
const v = obj[k];
|
||||||
|
copy[k] = !v || typeof v !== 'object' ? v : deepCopy(v);
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -390,51 +442,6 @@ function sessionStorageHash(name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function onBackgroundReady() {
|
|
||||||
return BG && BG.getStyles ? Promise.resolve() : new Promise(function ping(resolve) {
|
|
||||||
sendMessage({method: 'healthCheck'}, health => {
|
|
||||||
if (health !== undefined) {
|
|
||||||
BG = chrome.extension.getBackgroundPage();
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
setTimeout(ping, 0, resolve);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// in case Chrome haven't yet loaded the bg page and displays our page like edit/manage
|
|
||||||
function getStylesSafe(options) {
|
|
||||||
return onBackgroundReady()
|
|
||||||
.then(() => BG.getStyles(options));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function saveStyleSafe(style) {
|
|
||||||
return onBackgroundReady()
|
|
||||||
.then(() => BG.saveStyle(BG.deepCopy(style)))
|
|
||||||
.then(savedStyle => {
|
|
||||||
if (style.notify === false) {
|
|
||||||
handleUpdate(savedStyle, style);
|
|
||||||
}
|
|
||||||
return savedStyle;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function deleteStyleSafe({id, notify = true} = {}) {
|
|
||||||
return onBackgroundReady()
|
|
||||||
.then(() => BG.deleteStyle({id, notify}))
|
|
||||||
.then(() => {
|
|
||||||
if (!notify) {
|
|
||||||
handleDelete(id);
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function download(url, {
|
function download(url, {
|
||||||
method = url.includes('?') ? 'POST' : 'GET',
|
method = url.includes('?') ? 'POST' : 'GET',
|
||||||
body = url.includes('?') ? url.slice(url.indexOf('?')) : null,
|
body = url.includes('?') ? url.slice(url.indexOf('?')) : null,
|
||||||
|
@ -489,7 +496,7 @@ function invokeOrPostpone(isInvoke, fn, ...args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function openEditor(id) {
|
function openEditor({id}) {
|
||||||
let url = '/edit.html';
|
let url = '/edit.html';
|
||||||
if (id) {
|
if (id) {
|
||||||
url += `?id=${id}`;
|
url += `?id=${id}`;
|
||||||
|
|
133
js/prefs.js
133
js/prefs.js
|
@ -148,17 +148,13 @@ var prefs = new function Prefs() {
|
||||||
values[key] = value;
|
values[key] = value;
|
||||||
defineReadonlyProperty(this.readOnlyValues, key, value);
|
defineReadonlyProperty(this.readOnlyValues, key, value);
|
||||||
const hasChanged = !equal(value, oldValue);
|
const hasChanged = !equal(value, oldValue);
|
||||||
if (!fromBroadcast) {
|
if (!fromBroadcast || FIREFOX_NO_DOM_STORAGE) {
|
||||||
if (BG && BG !== window) {
|
localStorage[key] = typeof defaults[key] === 'object'
|
||||||
BG.prefs.set(key, BG.deepCopy(value), {broadcast, sync});
|
? JSON.stringify(value)
|
||||||
} else {
|
: value;
|
||||||
localStorage[key] = typeof defaults[key] === 'object'
|
}
|
||||||
? JSON.stringify(value)
|
if (!fromBroadcast && broadcast && hasChanged) {
|
||||||
: value;
|
this.broadcast(key, value, {sync});
|
||||||
if (broadcast && hasChanged) {
|
|
||||||
this.broadcast(key, value, {sync});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (hasChanged) {
|
if (hasChanged) {
|
||||||
const specific = onChange.specific.get(key);
|
const specific = onChange.specific.get(key);
|
||||||
|
@ -175,8 +171,6 @@ var prefs = new function Prefs() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
remove: key => this.set(key, undefined),
|
|
||||||
|
|
||||||
reset: key => this.set(key, deepCopy(defaults[key])),
|
reset: key => this.set(key, deepCopy(defaults[key])),
|
||||||
|
|
||||||
broadcast(key, value, {sync = true} = {}) {
|
broadcast(key, value, {sync = true} = {}) {
|
||||||
|
@ -226,62 +220,63 @@ var prefs = new function Prefs() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Unlike sync, HTML5 localStorage is ready at browser startup
|
{
|
||||||
// so we'll mirror the prefs to avoid using the wrong defaults
|
const importFromBG = () =>
|
||||||
// during the startup phase
|
API.getPrefs().then(prefs => {
|
||||||
for (const key in defaults) {
|
const props = {};
|
||||||
const defaultValue = defaults[key];
|
for (const id in prefs) {
|
||||||
let value = localStorage[key];
|
const value = prefs[id];
|
||||||
if (typeof value === 'string') {
|
values[id] = value;
|
||||||
switch (typeof defaultValue) {
|
props[id] = {value: deepCopy(value)};
|
||||||
case 'boolean':
|
}
|
||||||
value = value.toLowerCase() === 'true';
|
Object.defineProperties(this.readOnlyValues, props);
|
||||||
break;
|
});
|
||||||
case 'number':
|
// Unlike chrome.storage or messaging, HTML5 localStorage is synchronous and always ready,
|
||||||
value |= 0;
|
// so we'll mirror the prefs to avoid using the wrong defaults during the startup phase
|
||||||
break;
|
const importFromLocalStorage = () => {
|
||||||
case 'object':
|
|
||||||
value = tryJSONparse(value) || defaultValue;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (FIREFOX_NO_DOM_STORAGE && BG) {
|
|
||||||
value = BG.localStorage[key];
|
|
||||||
value = value === undefined ? defaultValue : value;
|
|
||||||
} else {
|
|
||||||
value = defaultValue;
|
|
||||||
}
|
|
||||||
if (BG === window) {
|
|
||||||
// when in bg page, .set() will write to localStorage
|
|
||||||
this.set(key, value, {broadcast: false, sync: false});
|
|
||||||
} else {
|
|
||||||
values[key] = value;
|
|
||||||
defineReadonlyProperty(this.readOnlyValues, key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!BG || BG === window) {
|
|
||||||
affectsIcon.forEach(key => this.broadcast(key, values[key], {sync: false}));
|
|
||||||
|
|
||||||
const importFromSync = (synced = {}) => {
|
|
||||||
for (const key in defaults) {
|
for (const key in defaults) {
|
||||||
if (key in synced) {
|
const defaultValue = defaults[key];
|
||||||
this.set(key, synced[key], {sync: false});
|
let value = localStorage[key];
|
||||||
}
|
if (typeof value === 'string') {
|
||||||
}
|
switch (typeof defaultValue) {
|
||||||
};
|
case 'boolean':
|
||||||
|
value = value.toLowerCase() === 'true';
|
||||||
getSync().get('settings', ({settings} = {}) => importFromSync(settings));
|
break;
|
||||||
|
case 'number':
|
||||||
chrome.storage.onChanged.addListener((changes, area) => {
|
value |= 0;
|
||||||
if (area === 'sync' && 'settings' in changes) {
|
break;
|
||||||
const synced = changes.settings.newValue;
|
case 'object':
|
||||||
if (synced) {
|
value = tryJSONparse(value) || defaultValue;
|
||||||
importFromSync(synced);
|
break;
|
||||||
|
}
|
||||||
|
} else if (FIREFOX_NO_DOM_STORAGE && BG) {
|
||||||
|
value = BG.localStorage[key];
|
||||||
|
value = value === undefined ? defaultValue : value;
|
||||||
|
localStorage[key] = value;
|
||||||
} else {
|
} else {
|
||||||
// user manually deleted our settings, we'll recreate them
|
value = defaultValue;
|
||||||
getSync().set({'settings': values});
|
}
|
||||||
|
if (BG === window) {
|
||||||
|
// when in bg page, .set() will write to localStorage
|
||||||
|
this.set(key, value, {broadcast: false, sync: false});
|
||||||
|
} else {
|
||||||
|
values[key] = value;
|
||||||
|
defineReadonlyProperty(this.readOnlyValues, key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
(FIREFOX_NO_DOM_STORAGE && !BG ? importFromBG() : importFromLocalStorage()).then(() => {
|
||||||
|
if (BG && BG !== window) return;
|
||||||
|
if (BG === window) {
|
||||||
|
affectsIcon.forEach(key => this.broadcast(key, values[key], {sync: false}));
|
||||||
|
getSync().get('settings', data => importFromSync.call(this, data.settings));
|
||||||
|
}
|
||||||
|
chrome.storage.onChanged.addListener((changes, area) => {
|
||||||
|
if (area === 'sync' && 'settings' in changes) {
|
||||||
|
importFromSync.call(this, changes.settings.newValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,6 +345,14 @@ var prefs = new function Prefs() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function importFromSync(synced = {}) {
|
||||||
|
for (const key in defaults) {
|
||||||
|
if (key in synced) {
|
||||||
|
this.set(key, synced[key], {sync: false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function defineReadonlyProperty(obj, key, value) {
|
function defineReadonlyProperty(obj, key, value) {
|
||||||
const copy = deepCopy(value);
|
const copy = deepCopy(value);
|
||||||
if (typeof copy === 'object') {
|
if (typeof copy === 'object') {
|
||||||
|
|
99
js/storage-util.js
Normal file
99
js/storage-util.js
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
/* global LZString loadScript */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var [chromeLocal, chromeSync] = [
|
||||||
|
chrome.storage.local,
|
||||||
|
chrome.storage.sync,
|
||||||
|
].map(storage => {
|
||||||
|
const wrapper = {
|
||||||
|
get(options) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
storage.get(options, data => resolve(data));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
set(data) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
storage.set(data, () => resolve(data));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
remove(keyOrKeys) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
storage.remove(keyOrKeys, resolve);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getValue(key) {
|
||||||
|
return wrapper.get(key).then(data => data[key]);
|
||||||
|
},
|
||||||
|
setValue(key, value) {
|
||||||
|
return wrapper.set({[key]: value});
|
||||||
|
},
|
||||||
|
loadLZStringScript() {
|
||||||
|
return Promise.resolve(
|
||||||
|
window.LZString ||
|
||||||
|
loadScript('/vendor/lz-string/lz-string-unsafe.js').then(() => {
|
||||||
|
window.LZString = window.LZStringUnsafe;
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
getLZValue(key) {
|
||||||
|
return wrapper.getLZValues([key]).then(data => data[key]);
|
||||||
|
},
|
||||||
|
getLZValues(keys) {
|
||||||
|
return Promise.all([
|
||||||
|
wrapper.get(keys),
|
||||||
|
wrapper.loadLZStringScript(),
|
||||||
|
]).then(([data = {}]) => {
|
||||||
|
for (const key of keys) {
|
||||||
|
const value = data[key];
|
||||||
|
data[key] = value && tryJSONparse(LZString.decompressFromUTF16(value));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setLZValue(key, value) {
|
||||||
|
return wrapper.loadLZStringScript().then(() =>
|
||||||
|
wrapper.set({
|
||||||
|
[key]: LZString.compressToUTF16(JSON.stringify(value)),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return wrapper;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function styleSectionsEqual({sections: a}, {sections: b}) {
|
||||||
|
if (!a || !b) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (a.length !== b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// order of sections should be identical to account for the case of multiple
|
||||||
|
// sections matching the same URL because the order of rules is part of cascading
|
||||||
|
return a.every((sectionA, index) => propertiesEqual(sectionA, b[index]));
|
||||||
|
|
||||||
|
function propertiesEqual(secA, secB) {
|
||||||
|
for (const name of ['urlPrefixes', 'urls', 'domains', 'regexps']) {
|
||||||
|
if (!equalOrEmpty(secA[name], secB[name], 'every', arrayMirrors)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return equalOrEmpty(secA.code, secB.code, 'substr', (a, b) => a === b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function equalOrEmpty(a, b, telltale, comparator) {
|
||||||
|
const typeA = a && typeof a[telltale] === 'function';
|
||||||
|
const typeB = b && typeof b[telltale] === 'function';
|
||||||
|
return (
|
||||||
|
(a === null || a === undefined || (typeA && !a.length)) &&
|
||||||
|
(b === null || b === undefined || (typeB && !b.length))
|
||||||
|
) || typeA && typeB && a.length === b.length && comparator(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayMirrors(array1, array2) {
|
||||||
|
return (
|
||||||
|
array1.every(el => array2.includes(el)) &&
|
||||||
|
array2.every(el => array1.includes(el))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
17
manage.html
17
manage.html
|
@ -150,13 +150,18 @@
|
||||||
<script src="js/prefs.js"></script>
|
<script src="js/prefs.js"></script>
|
||||||
<script src="content/apply.js"></script>
|
<script src="content/apply.js"></script>
|
||||||
<script src="js/localization.js"></script>
|
<script src="js/localization.js"></script>
|
||||||
|
<script src="js/storage-util.js"></script>
|
||||||
<script src="manage/filters.js"></script>
|
<script src="manage/filters.js"></script>
|
||||||
<script src="manage/updater-ui.js"></script>
|
|
||||||
<script src="manage/object-diff.js"></script>
|
|
||||||
<script src="vendor-overwrites/colorpicker/colorpicker.js"></script>
|
|
||||||
<script src="manage/config-dialog.js"></script>
|
|
||||||
<script src="manage/sort.js"></script>
|
<script src="manage/sort.js"></script>
|
||||||
<script src="manage/manage.js"></script>
|
<script src="manage/manage.js"></script>
|
||||||
|
|
||||||
|
<script src="vendor-overwrites/colorpicker/colorpicker.js" async></script>
|
||||||
|
<script src="manage/config-dialog.js" async></script>
|
||||||
|
<script src="manage/updater-ui.js" async></script>
|
||||||
|
<script src="manage/object-diff.js" async></script>
|
||||||
|
<script src="manage/import-export.js" async></script>
|
||||||
|
<script src="msgbox/msgbox.js" async></script>
|
||||||
|
<script src="manage/incremental-search.js" async></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body id="stylus-manage" i18n-dragndrop-hint="dragDropMessage">
|
<body id="stylus-manage" i18n-dragndrop-hint="dragDropMessage">
|
||||||
|
@ -358,10 +363,6 @@
|
||||||
|
|
||||||
<div id="installed"></div>
|
<div id="installed"></div>
|
||||||
|
|
||||||
<script src="manage/import-export.js"></script>
|
|
||||||
<script src="msgbox/msgbox.js"></script>
|
|
||||||
<script src="manage/incremental-search.js" async></script>
|
|
||||||
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none !important;">
|
<svg xmlns="http://www.w3.org/2000/svg" style="display: none !important;">
|
||||||
<symbol id="svg-icon-checked" viewBox="0 0 1000 1000">
|
<symbol id="svg-icon-checked" viewBox="0 0 1000 1000">
|
||||||
<path fill-rule="evenodd" d="M983.2,184.3L853,69.8c-4-3.5-9.3-5.3-14.5-5c-5.3,0.4-10.3,2.8-13.8,6.8L352.3,609.2L184.4,386.9c-3.2-4.2-8-7-13.2-7.8c-5.3-0.8-10.6,0.6-14.9,3.9L18,487.5c-8.8,6.7-10.6,19.3-3.9,28.1L325,927.2c3.6,4.8,9.3,7.7,15.3,8c0.2,0,0.5,0,0.7,0c5.8,0,11.3-2.5,15.1-6.8L985,212.6C992.3,204.3,991.5,191.6,983.2,184.3z"/>
|
<path fill-rule="evenodd" d="M983.2,184.3L853,69.8c-4-3.5-9.3-5.3-14.5-5c-5.3,0.4-10.3,2.8-13.8,6.8L352.3,609.2L184.4,386.9c-3.2-4.2-8-7-13.2-7.8c-5.3-0.8-10.6,0.6-14.9,3.9L18,487.5c-8.8,6.7-10.6,19.3-3.9,28.1L325,927.2c3.6,4.8,9.3,7.7,15.3,8c0.2,0,0.5,0,0.7,0c5.8,0,11.3-2.5,15.1-6.8L985,212.6C992.3,204.3,991.5,191.6,983.2,184.3z"/>
|
||||||
|
|
|
@ -107,7 +107,7 @@ function configDialog(style) {
|
||||||
buttons.close.textContent = t(someDirty ? 'confirmCancel' : 'confirmClose');
|
buttons.close.textContent = t(someDirty ? 'confirmCancel' : 'confirmClose');
|
||||||
}
|
}
|
||||||
|
|
||||||
function save({anyChangeIsDirty = false} = {}) {
|
function save({anyChangeIsDirty = false} = {}, bgStyle) {
|
||||||
if (saving) {
|
if (saving) {
|
||||||
debounce(save, 0, ...arguments);
|
debounce(save, 0, ...arguments);
|
||||||
return;
|
return;
|
||||||
|
@ -116,11 +116,18 @@ function configDialog(style) {
|
||||||
!vars.some(va => va.dirty || anyChangeIsDirty && va.value !== va.savedValue)) {
|
!vars.some(va => va.dirty || anyChangeIsDirty && va.value !== va.savedValue)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!bgStyle) {
|
||||||
|
API.getStyles({id: style.id, omitCode: !BG})
|
||||||
|
.then(([bgStyle]) => save({anyChangeIsDirty}, bgStyle || {}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
style = style.sections ? Object.assign({}, style) : style;
|
||||||
style.enabled = true;
|
style.enabled = true;
|
||||||
style.reason = 'config';
|
style.reason = 'config';
|
||||||
|
style.sourceCode = null;
|
||||||
|
style.sections = null;
|
||||||
const styleVars = style.usercssData.vars;
|
const styleVars = style.usercssData.vars;
|
||||||
const bgStyle = BG.cachedStyles.byId.get(style.id);
|
const bgVars = (bgStyle.usercssData || {}).vars || {};
|
||||||
const bgVars = bgStyle && (bgStyle.usercssData || {}).vars || {};
|
|
||||||
const invalid = [];
|
const invalid = [];
|
||||||
let numValid = 0;
|
let numValid = 0;
|
||||||
for (const va of vars) {
|
for (const va of vars) {
|
||||||
|
@ -164,9 +171,9 @@ function configDialog(style) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
saving = true;
|
saving = true;
|
||||||
return BG.usercssHelper.save(BG.deepCopy(style))
|
return API.saveUsercss(style)
|
||||||
.then(saved => {
|
.then(saved => {
|
||||||
varsInitial = getInitialValues(deepCopy(saved.usercssData.vars));
|
varsInitial = getInitialValues(saved.usercssData.vars);
|
||||||
vars.forEach(va => onchange({target: va.input, justSaved: true}));
|
vars.forEach(va => onchange({target: va.input, justSaved: true}));
|
||||||
renderValues();
|
renderValues();
|
||||||
updateButtons();
|
updateButtons();
|
||||||
|
|
|
@ -29,7 +29,7 @@ HTMLSelectElement.prototype.adjustWidth = function () {
|
||||||
parent.replaceChild(this, singleSelect);
|
parent.replaceChild(this, singleSelect);
|
||||||
};
|
};
|
||||||
|
|
||||||
onDOMready().then(onBackgroundReady).then(() => {
|
onDOMready().then(() => {
|
||||||
$('#search').oninput = searchStyles;
|
$('#search').oninput = searchStyles;
|
||||||
if (urlFilterParam) {
|
if (urlFilterParam) {
|
||||||
$('#search').value = 'url:' + urlFilterParam;
|
$('#search').value = 'url:' + urlFilterParam;
|
||||||
|
@ -169,14 +169,17 @@ function filterAndAppend({entry, container}) {
|
||||||
if (!filtersSelector.hide || !entry.matches(filtersSelector.hide)) {
|
if (!filtersSelector.hide || !entry.matches(filtersSelector.hide)) {
|
||||||
entry.classList.add('hidden');
|
entry.classList.add('hidden');
|
||||||
}
|
}
|
||||||
} else if ($('#search').value.trim()) {
|
|
||||||
searchStyles({immediately: true, container});
|
|
||||||
}
|
}
|
||||||
reapplyFilter(container);
|
reapplyFilter(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function reapplyFilter(container = installed) {
|
function reapplyFilter(container = installed, alreadySearched) {
|
||||||
|
if (!alreadySearched && $('#search').value.trim()) {
|
||||||
|
searchStyles({immediately: true, container})
|
||||||
|
.then(() => reapplyFilter(container, true));
|
||||||
|
return;
|
||||||
|
}
|
||||||
// A: show
|
// A: show
|
||||||
let toHide = [];
|
let toHide = [];
|
||||||
let toUnhide = [];
|
let toUnhide = [];
|
||||||
|
@ -189,9 +192,6 @@ function reapplyFilter(container = installed) {
|
||||||
if (toUnhide instanceof DocumentFragment) {
|
if (toUnhide instanceof DocumentFragment) {
|
||||||
installed.appendChild(toUnhide);
|
installed.appendChild(toUnhide);
|
||||||
return;
|
return;
|
||||||
} else if (toUnhide.length && $('#search').value.trim()) {
|
|
||||||
searchStyles({immediately: true, container: toUnhide});
|
|
||||||
filterContainer({hide: false});
|
|
||||||
}
|
}
|
||||||
// filtering needed or a single-element job from handleUpdate()
|
// filtering needed or a single-element job from handleUpdate()
|
||||||
for (const entry of toUnhide.children || toUnhide) {
|
for (const entry of toUnhide.children || toUnhide) {
|
||||||
|
@ -251,16 +251,12 @@ function reapplyFilter(container = installed) {
|
||||||
|
|
||||||
|
|
||||||
function showFiltersStats() {
|
function showFiltersStats() {
|
||||||
if (!BG.cachedStyles.list) {
|
|
||||||
debounce(showFiltersStats, 100);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const active = filtersSelector.hide !== '';
|
const active = filtersSelector.hide !== '';
|
||||||
$('#filters summary').classList.toggle('active', active);
|
$('#filters summary').classList.toggle('active', active);
|
||||||
$('#reset-filters').disabled = !active;
|
$('#reset-filters').disabled = !active;
|
||||||
const numTotal = BG.cachedStyles.list.length;
|
const numTotal = installed.children.length;
|
||||||
const numHidden = installed.getElementsByClassName('entry hidden').length;
|
const numHidden = installed.getElementsByClassName('entry hidden').length;
|
||||||
const numShown = Math.min(numTotal - numHidden, installed.children.length);
|
const numShown = numTotal - numHidden;
|
||||||
if (filtersSelector.numShown !== numShown ||
|
if (filtersSelector.numShown !== numShown ||
|
||||||
filtersSelector.numTotal !== numTotal) {
|
filtersSelector.numTotal !== numTotal) {
|
||||||
filtersSelector.numShown = numShown;
|
filtersSelector.numShown = numShown;
|
||||||
|
@ -273,87 +269,34 @@ function showFiltersStats() {
|
||||||
|
|
||||||
|
|
||||||
function searchStyles({immediately, container}) {
|
function searchStyles({immediately, container}) {
|
||||||
const searchElement = $('#search');
|
const el = $('#search');
|
||||||
const value = searchElement.value.trim();
|
const query = el.value.trim();
|
||||||
const urlMode = /^\s*url:/i.test(value);
|
if (query === el.lastValue && !immediately && !container) {
|
||||||
const query = urlMode
|
|
||||||
? value.replace(/^\s*url:/i, '')
|
|
||||||
: value.toLocaleLowerCase();
|
|
||||||
if (query === searchElement.lastValue && !immediately && !container) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!immediately) {
|
if (!immediately) {
|
||||||
debounce(searchStyles, 150, {immediately: true});
|
debounce(searchStyles, 150, {immediately: true});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
searchElement.lastValue = query;
|
el.lastValue = query;
|
||||||
|
|
||||||
const rx = query.startsWith('/') && query.indexOf('/', 1) > 0 &&
|
|
||||||
tryRegExp(...(value.match(/^\s*\/(.*?)\/([gimsuy]*)\s*$/) || []).slice(1));
|
|
||||||
const words = rx ? null :
|
|
||||||
query.startsWith('"') && query.endsWith('"') ? [value.trim().slice(1, -1)] :
|
|
||||||
query.split(/\s+/).filter(s => s.length > 1);
|
|
||||||
if (words && !words.length) {
|
|
||||||
words.push(query);
|
|
||||||
}
|
|
||||||
const entries = container && container.children || container || installed.children;
|
const entries = container && container.children || container || installed.children;
|
||||||
const siteStyleIds = urlMode &&
|
return API.searchDB({
|
||||||
new Set(BG.filterStyles({matchUrl: query}).map(style => style.id));
|
query,
|
||||||
let needsRefilter = false;
|
ids: [...entries].map(el => el.styleId),
|
||||||
for (const entry of entries) {
|
}).then(ids => {
|
||||||
let isMatching = !query || words && !words.length;
|
ids = new Set(ids);
|
||||||
if (!isMatching) {
|
let needsRefilter = false;
|
||||||
const style = urlMode ? siteStyleIds.has(entry.styleId) :
|
for (const entry of entries) {
|
||||||
BG.cachedStyles.byId.get(entry.styleId) || {};
|
const isMatching = ids.has(entry.styleId);
|
||||||
isMatching = Boolean(style && (
|
if (entry.classList.contains('not-matching') !== !isMatching) {
|
||||||
urlMode ||
|
entry.classList.toggle('not-matching', !isMatching);
|
||||||
isMatchingText(style.name) ||
|
needsRefilter = true;
|
||||||
style.url && isMatchingText(style.url) ||
|
|
||||||
style.sourceCode && isMatchingText(style.sourceCode) ||
|
|
||||||
isMatchingStyle(style)));
|
|
||||||
}
|
|
||||||
if (entry.classList.contains('not-matching') !== !isMatching) {
|
|
||||||
entry.classList.toggle('not-matching', !isMatching);
|
|
||||||
needsRefilter = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (needsRefilter && !container) {
|
|
||||||
filterOnChange({forceRefilter: true});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
|
|
||||||
function isMatchingStyle(style) {
|
|
||||||
for (const section of style.sections) {
|
|
||||||
for (const prop in section) {
|
|
||||||
const value = section[prop];
|
|
||||||
switch (typeof value) {
|
|
||||||
case 'string':
|
|
||||||
if (isMatchingText(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'object':
|
|
||||||
for (const str of value) {
|
|
||||||
if (isMatchingText(str)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (needsRefilter && !container) {
|
||||||
|
filterOnChange({forceRefilter: true});
|
||||||
function isMatchingText(text) {
|
|
||||||
if (rx) {
|
|
||||||
return rx.test(text);
|
|
||||||
}
|
}
|
||||||
for (let pass = 1; pass <= 2; pass++) {
|
return container;
|
||||||
if (words.every(word => text.includes(word))) {
|
});
|
||||||
return true;
|
|
||||||
}
|
|
||||||
text = text.toLocaleLowerCase();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global messageBox, handleUpdate, applyOnMessage */
|
/* global messageBox handleUpdate applyOnMessage styleSectionsEqual */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const STYLISH_DUMP_FILE_EXT = '.txt';
|
const STYLISH_DUMP_FILE_EXT = '.txt';
|
||||||
|
@ -41,7 +41,7 @@ function importFromFile({fileTypeFilter, file} = {}) {
|
||||||
importFromString(text) :
|
importFromString(text) :
|
||||||
getOwnTab().then(tab => {
|
getOwnTab().then(tab => {
|
||||||
tab.url = URL.createObjectURL(new Blob([text], {type: 'text/css'}));
|
tab.url = URL.createObjectURL(new Blob([text], {type: 'text/css'}));
|
||||||
return BG.usercssHelper.openInstallPage(tab, {direct: true})
|
return API.installUsercss({direct: true}, {tab})
|
||||||
.then(() => URL.revokeObjectURL(tab.url));
|
.then(() => URL.revokeObjectURL(tab.url));
|
||||||
})
|
})
|
||||||
).then(numStyles => {
|
).then(numStyles => {
|
||||||
|
@ -56,17 +56,17 @@ function importFromFile({fileTypeFilter, file} = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function importFromString(jsonString) {
|
function importFromString(jsonString, oldStyles) {
|
||||||
if (!BG) {
|
if (!oldStyles) {
|
||||||
onBackgroundReady().then(() => importFromString(jsonString));
|
API.getStyles().then(styles => importFromString(jsonString, styles));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// create objects in background context
|
const json = tryJSONparse(jsonString) || [];
|
||||||
const json = BG.tryJSONparse(jsonString) || [];
|
|
||||||
if (typeof json.slice !== 'function') {
|
if (typeof json.slice !== 'function') {
|
||||||
json.length = 0;
|
json.length = 0;
|
||||||
}
|
}
|
||||||
const oldStyles = json.length && BG.deepCopy(BG.cachedStyles.list || []);
|
const oldStylesById = new Map(
|
||||||
|
oldStyles.map(style => [style.id, style]));
|
||||||
const oldStylesByName = json.length && new Map(
|
const oldStylesByName = json.length && new Map(
|
||||||
oldStyles.map(style => [style.name.trim(), style]));
|
oldStyles.map(style => [style.name.trim(), style]));
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ function importFromString(jsonString) {
|
||||||
const info = analyze(item);
|
const info = analyze(item);
|
||||||
if (info) {
|
if (info) {
|
||||||
// using saveStyle directly since json was parsed in background page context
|
// using saveStyle directly since json was parsed in background page context
|
||||||
return BG.saveStyle(Object.assign(item, SAVE_OPTIONS))
|
return API.saveStyle(Object.assign(item, SAVE_OPTIONS))
|
||||||
.then(style => account({style, info, resolve}));
|
.then(style => account({style, info, resolve}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ function importFromString(jsonString) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
item.name = item.name.trim();
|
item.name = item.name.trim();
|
||||||
const byId = BG.cachedStyles.byId.get(item.id);
|
const byId = oldStylesById.get(item.id);
|
||||||
const byName = oldStylesByName.get(item.name);
|
const byName = oldStylesByName.get(item.name);
|
||||||
oldStylesByName.delete(item.name);
|
oldStylesByName.delete(item.name);
|
||||||
let oldStyle;
|
let oldStyle;
|
||||||
|
@ -129,7 +129,7 @@ function importFromString(jsonString) {
|
||||||
const metaEqual = oldStyleKeys &&
|
const metaEqual = oldStyleKeys &&
|
||||||
oldStyleKeys.length === Object.keys(item).length &&
|
oldStyleKeys.length === Object.keys(item).length &&
|
||||||
oldStyleKeys.every(k => k === 'sections' || oldStyle[k] === item[k]);
|
oldStyleKeys.every(k => k === 'sections' || oldStyle[k] === item[k]);
|
||||||
const codeEqual = oldStyle && BG.styleSectionsEqual(oldStyle, item);
|
const codeEqual = oldStyle && styleSectionsEqual(oldStyle, item);
|
||||||
if (metaEqual && codeEqual) {
|
if (metaEqual && codeEqual) {
|
||||||
stats.unchanged.names.push(oldStyle.name);
|
stats.unchanged.names.push(oldStyle.name);
|
||||||
stats.unchanged.ids.push(oldStyle.id);
|
stats.unchanged.ids.push(oldStyle.id);
|
||||||
|
@ -237,10 +237,10 @@ function importFromString(jsonString) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const id = newIds[index++];
|
const id = newIds[index++];
|
||||||
deleteStyleSafe({id, notify: false}).then(id => {
|
API.deleteStyle({id, notify: false}).then(id => {
|
||||||
const oldStyle = oldStylesById.get(id);
|
const oldStyle = oldStylesById.get(id);
|
||||||
if (oldStyle) {
|
if (oldStyle) {
|
||||||
saveStyleSafe(Object.assign(oldStyle, SAVE_OPTIONS))
|
API.saveStyle(Object.assign(oldStyle, SAVE_OPTIONS))
|
||||||
.then(undoNextId);
|
.then(undoNextId);
|
||||||
} else {
|
} else {
|
||||||
undoNextId();
|
undoNextId();
|
||||||
|
@ -293,7 +293,7 @@ function importFromString(jsonString) {
|
||||||
chrome.webNavigation.getAllFrames({tabId}, frames => {
|
chrome.webNavigation.getAllFrames({tabId}, frames => {
|
||||||
frames = frames && frames[0] ? frames : [{frameId: 0}];
|
frames = frames && frames[0] ? frames : [{frameId: 0}];
|
||||||
frames.forEach(({frameId}) =>
|
frames.forEach(({frameId}) =>
|
||||||
getStylesSafe({matchUrl: tab.url, enabled: true, asHash: true}).then(styles => {
|
API.getStyles({matchUrl: tab.url, enabled: true, asHash: true}).then(styles => {
|
||||||
const message = {method: 'styleReplaceAll', tabId, frameId, styles};
|
const message = {method: 'styleReplaceAll', tabId, frameId, styles};
|
||||||
if (tab.id === ownTab.id) {
|
if (tab.id === ownTab.id) {
|
||||||
applyOnMessage(message);
|
applyOnMessage(message);
|
||||||
|
@ -301,7 +301,7 @@ function importFromString(jsonString) {
|
||||||
invokeOrPostpone(tab.active, sendMessage, message, ignoreChromeError);
|
invokeOrPostpone(tab.active, sendMessage, message, ignoreChromeError);
|
||||||
}
|
}
|
||||||
if (frameId === 0) {
|
if (frameId === 0) {
|
||||||
setTimeout(BG.updateIcon, 0, tab, styles);
|
setTimeout(API.updateIcon, 0, tab, styles);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
if (resolve) {
|
if (resolve) {
|
||||||
|
@ -314,7 +314,7 @@ function importFromString(jsonString) {
|
||||||
|
|
||||||
|
|
||||||
$('#file-all-styles').onclick = () => {
|
$('#file-all-styles').onclick = () => {
|
||||||
getStylesSafe().then(styles => {
|
API.getStyles().then(styles => {
|
||||||
const text = JSON.stringify(styles, null, '\t');
|
const text = JSON.stringify(styles, null, '\t');
|
||||||
const blob = new Blob([text], {type: 'application/json'});
|
const blob = new Blob([text], {type: 'application/json'});
|
||||||
const objectURL = URL.createObjectURL(blob);
|
const objectURL = URL.createObjectURL(blob);
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
/* global messageBox, getStyleWithNoCode, retranslateCSS */
|
/*
|
||||||
/* global filtersSelector, filterAndAppend */
|
global messageBox getStyleWithNoCode retranslateCSS
|
||||||
/* global checkUpdate, handleUpdateInstalled */
|
global filtersSelector filterAndAppend urlFilterParam
|
||||||
/* global objectDiff */
|
global checkUpdate handleUpdateInstalled
|
||||||
/* global configDialog */
|
global objectDiff
|
||||||
/* global sorter */
|
global configDialog
|
||||||
|
global sorter
|
||||||
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
let installed;
|
let installed;
|
||||||
|
@ -30,14 +32,13 @@ const OWN_ICON = chrome.runtime.getManifest().icons['16'];
|
||||||
const handleEvent = {};
|
const handleEvent = {};
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
getStylesSafe(),
|
API.getStyles({omitCode: !BG}),
|
||||||
|
urlFilterParam && API.searchDB({query: 'url:' + urlFilterParam}),
|
||||||
onDOMready().then(initGlobalEvents),
|
onDOMready().then(initGlobalEvents),
|
||||||
]).then(([styles]) => {
|
]).then(args => {
|
||||||
showStyles(styles);
|
showStyles(...args);
|
||||||
});
|
});
|
||||||
|
|
||||||
dieOnNullBackground();
|
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener(onRuntimeMessage);
|
chrome.runtime.onMessage.addListener(onRuntimeMessage);
|
||||||
|
|
||||||
function onRuntimeMessage(msg) {
|
function onRuntimeMessage(msg) {
|
||||||
|
@ -107,7 +108,7 @@ function initGlobalEvents() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function showStyles(styles = []) {
|
function showStyles(styles = [], matchUrlIds) {
|
||||||
const sorted = sorter.sort({
|
const sorted = sorter.sort({
|
||||||
styles: styles.map(style => ({
|
styles: styles.map(style => ({
|
||||||
style,
|
style,
|
||||||
|
@ -137,7 +138,13 @@ function showStyles(styles = []) {
|
||||||
// eslint-disable-next-line no-unmodified-loop-condition
|
// eslint-disable-next-line no-unmodified-loop-condition
|
||||||
(shouldRenderAll || ++rendered < 20 || performance.now() - t0 < 10)
|
(shouldRenderAll || ++rendered < 20 || performance.now() - t0 < 10)
|
||||||
) {
|
) {
|
||||||
renderBin.appendChild(createStyleElement(sorted[index++]));
|
const info = sorted[index++];
|
||||||
|
const entry = createStyleElement(info);
|
||||||
|
if (matchUrlIds && !matchUrlIds.includes(info.style.id)) {
|
||||||
|
entry.classList.add('not-matching');
|
||||||
|
rendered--;
|
||||||
|
}
|
||||||
|
renderBin.appendChild(entry);
|
||||||
}
|
}
|
||||||
filterAndAppend({container: renderBin});
|
filterAndAppend({container: renderBin});
|
||||||
if (index < sorted.length) {
|
if (index < sorted.length) {
|
||||||
|
@ -277,7 +284,7 @@ function createStyleTargetsElement({entry, style, iconsOnly}) {
|
||||||
|
|
||||||
|
|
||||||
function recreateStyleTargets({styles, iconsOnly = false} = {}) {
|
function recreateStyleTargets({styles, iconsOnly = false} = {}) {
|
||||||
Promise.resolve(styles || getStylesSafe()).then(styles => {
|
Promise.resolve(styles || API.getStyles()).then(styles => {
|
||||||
for (const style of styles) {
|
for (const style of styles) {
|
||||||
const entry = $(ENTRY_ID_PREFIX + style.id);
|
const entry = $(ENTRY_ID_PREFIX + style.id);
|
||||||
if (entry) {
|
if (entry) {
|
||||||
|
@ -391,7 +398,7 @@ Object.assign(handleEvent, {
|
||||||
},
|
},
|
||||||
|
|
||||||
toggle(event, entry) {
|
toggle(event, entry) {
|
||||||
saveStyleSafe({
|
API.saveStyle({
|
||||||
id: entry.styleId,
|
id: entry.styleId,
|
||||||
enabled: this.matches('.enable') || this.checked,
|
enabled: this.matches('.enable') || this.checked,
|
||||||
});
|
});
|
||||||
|
@ -399,39 +406,30 @@ Object.assign(handleEvent, {
|
||||||
|
|
||||||
check(event, entry) {
|
check(event, entry) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
checkUpdate(entry);
|
checkUpdate(entry, {single: true});
|
||||||
},
|
},
|
||||||
|
|
||||||
update(event, entry) {
|
update(event, entry) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const request = Object.assign(entry.updatedCode, {
|
const json = entry.updatedCode;
|
||||||
id: entry.styleId,
|
json.id = entry.styleId;
|
||||||
reason: 'update',
|
json.reason = 'update';
|
||||||
});
|
API[json.usercssData ? 'saveUsercss' : 'saveStyle'](json);
|
||||||
if (entry.updatedCode.usercssData) {
|
|
||||||
onBackgroundReady()
|
|
||||||
.then(() => BG.usercssHelper.save(request));
|
|
||||||
} else {
|
|
||||||
// update everything but name
|
|
||||||
request.name = null;
|
|
||||||
saveStyleSafe(request);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
delete(event, entry) {
|
delete(event, entry) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const id = entry.styleId;
|
const id = entry.styleId;
|
||||||
const {name} = BG.cachedStyles.byId.get(id) || {};
|
|
||||||
animateElement(entry);
|
animateElement(entry);
|
||||||
messageBox({
|
messageBox({
|
||||||
title: t('deleteStyleConfirm'),
|
title: t('deleteStyleConfirm'),
|
||||||
contents: name,
|
contents: entry.styleMeta.name,
|
||||||
className: 'danger center',
|
className: 'danger center',
|
||||||
buttons: [t('confirmDelete'), t('confirmCancel')],
|
buttons: [t('confirmDelete'), t('confirmCancel')],
|
||||||
})
|
})
|
||||||
.then(({button}) => {
|
.then(({button}) => {
|
||||||
if (button === 0) {
|
if (button === 0) {
|
||||||
deleteStyleSafe({id});
|
API.deleteStyle({id});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -525,7 +523,7 @@ function handleUpdate(style, {reason, method} = {}) {
|
||||||
sorter.update();
|
sorter.update();
|
||||||
if (!entry.matches('.hidden') && reason !== 'import') {
|
if (!entry.matches('.hidden') && reason !== 'import') {
|
||||||
animateElement(entry);
|
animateElement(entry);
|
||||||
scrollElementIntoView(entry);
|
requestAnimationFrame(() => scrollElementIntoView(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleToggledOrCodeOnly() {
|
function handleToggledOrCodeOnly() {
|
||||||
|
@ -606,7 +604,7 @@ function switchUI({styleOnly} = {}) {
|
||||||
const missingFavicons = newUI.enabled && newUI.favicons && !$('.applies-to img');
|
const missingFavicons = newUI.enabled && newUI.favicons && !$('.applies-to img');
|
||||||
if (changed.enabled || (missingFavicons && !createStyleElement.parts)) {
|
if (changed.enabled || (missingFavicons && !createStyleElement.parts)) {
|
||||||
installed.textContent = '';
|
installed.textContent = '';
|
||||||
getStylesSafe().then(showStyles);
|
API.getStyles().then(showStyles);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (changed.targets) {
|
if (changed.targets) {
|
||||||
|
@ -645,28 +643,3 @@ function usePrefsDuringPageLoad() {
|
||||||
}
|
}
|
||||||
$$('#header select').forEach(el => el.adjustWidth());
|
$$('#header select').forEach(el => el.adjustWidth());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: remove when these bugs are fixed in FF
|
|
||||||
function dieOnNullBackground() {
|
|
||||||
if (!FIREFOX || BG) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendMessage({method: 'healthCheck'}, health => {
|
|
||||||
if (health && !chrome.extension.getBackgroundPage()) {
|
|
||||||
onDOMready().then(() => {
|
|
||||||
sendMessage({method: 'getStyles'}, showStyles);
|
|
||||||
messageBox({
|
|
||||||
title: 'Stylus',
|
|
||||||
className: 'danger center',
|
|
||||||
contents: t('dysfunctionalBackgroundConnection'),
|
|
||||||
onshow: () => {
|
|
||||||
$('#message-box-close-icon').remove();
|
|
||||||
window.removeEventListener('keydown', messageBox.listeners.key, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
document.documentElement.style.pointerEvents = 'none';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -129,7 +129,7 @@ const sorter = (() => {
|
||||||
styles: current.map(entry => ({
|
styles: current.map(entry => ({
|
||||||
entry,
|
entry,
|
||||||
name: entry.styleNameLowerCase + '\n' + entry.styleMeta.name,
|
name: entry.styleNameLowerCase + '\n' + entry.styleMeta.name,
|
||||||
style: BG.cachedStyles.byId.get(entry.styleId),
|
style: entry.styleMeta,
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
if (current.some((entry, index) => entry !== sorted[index].entry)) {
|
if (current.some((entry, index) => entry !== sorted[index].entry)) {
|
||||||
|
|
|
@ -29,41 +29,51 @@ function applyUpdateAll() {
|
||||||
|
|
||||||
function checkUpdateAll() {
|
function checkUpdateAll() {
|
||||||
document.body.classList.add('update-in-progress');
|
document.body.classList.add('update-in-progress');
|
||||||
$('#check-all-updates').disabled = true;
|
const btnCheck = $('#check-all-updates');
|
||||||
$('#check-all-updates-force').classList.add('hidden');
|
const btnCheckForce = $('#check-all-updates-force');
|
||||||
$('#apply-all-updates').classList.add('hidden');
|
const btnApply = $('#apply-all-updates');
|
||||||
$('#update-all-no-updates').classList.add('hidden');
|
const noUpdates = $('#update-all-no-updates');
|
||||||
|
btnCheck.disabled = true;
|
||||||
|
btnCheckForce.classList.add('hidden');
|
||||||
|
btnApply.classList.add('hidden');
|
||||||
|
noUpdates.classList.add('hidden');
|
||||||
|
|
||||||
const ignoreDigest = this && this.id === 'check-all-updates-force';
|
const ignoreDigest = this && this.id === 'check-all-updates-force';
|
||||||
$$('.updatable:not(.can-update)' + (ignoreDigest ? '' : ':not(.update-problem)'))
|
$$('.updatable:not(.can-update)' + (ignoreDigest ? '' : ':not(.update-problem)'))
|
||||||
.map(el => checkUpdate(el, {single: false}));
|
.map(checkUpdate);
|
||||||
|
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let checked = 0;
|
let checked = 0;
|
||||||
let skippedEdited = 0;
|
let skippedEdited = 0;
|
||||||
let updated = 0;
|
let updated = 0;
|
||||||
|
|
||||||
BG.updater.checkAllStyles({observer, save: false, ignoreDigest}).then(done);
|
chrome.runtime.onConnect.addListener(function onConnect(port) {
|
||||||
|
if (port.name !== 'updater') return;
|
||||||
|
port.onMessage.addListener(observer);
|
||||||
|
chrome.runtime.onConnect.removeListener(onConnect);
|
||||||
|
});
|
||||||
|
|
||||||
function observer(state, value, details) {
|
API.updateCheckAll({
|
||||||
switch (state) {
|
save: false,
|
||||||
case BG.updater.COUNT:
|
observe: true,
|
||||||
total = value;
|
ignoreDigest,
|
||||||
break;
|
}).then(done);
|
||||||
case BG.updater.UPDATED:
|
|
||||||
if (++updated === 1) {
|
function observer(info) {
|
||||||
$('#apply-all-updates').disabled = true;
|
if ('count' in info) {
|
||||||
$('#apply-all-updates').classList.remove('hidden');
|
total = info.count;
|
||||||
}
|
}
|
||||||
$('#apply-all-updates').dataset.value = updated;
|
if (info.updated) {
|
||||||
// fallthrough
|
if (++updated === 1) {
|
||||||
case BG.updater.SKIPPED:
|
btnApply.disabled = true;
|
||||||
checked++;
|
btnApply.classList.remove('hidden');
|
||||||
if (details === BG.updater.EDITED || details === BG.updater.MAYBE_EDITED) {
|
}
|
||||||
skippedEdited++;
|
btnApply.dataset.value = updated;
|
||||||
}
|
}
|
||||||
reportUpdateState(state, value, details);
|
if (info.updated || info.error) {
|
||||||
break;
|
checked++;
|
||||||
|
skippedEdited += [info.STATES.EDITED, info.STATES.MAYBE_EDITED].includes(info.error);
|
||||||
|
reportUpdateState(info);
|
||||||
}
|
}
|
||||||
const progress = $('#update-progress');
|
const progress = $('#update-progress');
|
||||||
const maxWidth = progress.parentElement.clientWidth;
|
const maxWidth = progress.parentElement.clientWidth;
|
||||||
|
@ -72,35 +82,34 @@ function checkUpdateAll() {
|
||||||
|
|
||||||
function done() {
|
function done() {
|
||||||
document.body.classList.remove('update-in-progress');
|
document.body.classList.remove('update-in-progress');
|
||||||
$('#check-all-updates').disabled = total === 0;
|
btnCheck.disabled = total === 0;
|
||||||
$('#apply-all-updates').disabled = false;
|
btnApply.disabled = false;
|
||||||
renderUpdatesOnlyFilter({check: updated + skippedEdited > 0});
|
renderUpdatesOnlyFilter({check: updated + skippedEdited > 0});
|
||||||
if (!updated) {
|
if (!updated) {
|
||||||
$('#update-all-no-updates').dataset.skippedEdited = skippedEdited > 0;
|
noUpdates.dataset.skippedEdited = skippedEdited > 0;
|
||||||
$('#update-all-no-updates').classList.remove('hidden');
|
noUpdates.classList.remove('hidden');
|
||||||
$('#check-all-updates-force').classList.toggle('hidden', skippedEdited === 0);
|
btnCheckForce.classList.toggle('hidden', skippedEdited === 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function checkUpdate(entry, {single = true} = {}) {
|
function checkUpdate(entry, {single} = {}) {
|
||||||
$('.update-note', entry).textContent = t('checkingForUpdate');
|
$('.update-note', entry).textContent = t('checkingForUpdate');
|
||||||
$('.check-update', entry).title = '';
|
$('.check-update', entry).title = '';
|
||||||
if (single) {
|
if (single) {
|
||||||
BG.updater.checkStyle({
|
API.updateCheck({
|
||||||
save: false,
|
save: false,
|
||||||
|
id: entry.styleId,
|
||||||
ignoreDigest: entry.classList.contains('update-problem'),
|
ignoreDigest: entry.classList.contains('update-problem'),
|
||||||
style: BG.cachedStyles.byId.get(entry.styleId),
|
}).then(reportUpdateState);
|
||||||
observer: reportUpdateState,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
entry.classList.remove('checking-update', 'no-update', 'update-problem');
|
entry.classList.remove('checking-update', 'no-update', 'update-problem');
|
||||||
entry.classList.add('checking-update');
|
entry.classList.add('checking-update');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function reportUpdateState(state, style, details) {
|
function reportUpdateState({updated, style, error, STATES}) {
|
||||||
const entry = $(ENTRY_ID_PREFIX + style.id);
|
const entry = $(ENTRY_ID_PREFIX + style.id);
|
||||||
const newClasses = new Map([
|
const newClasses = new Map([
|
||||||
/*
|
/*
|
||||||
|
@ -117,43 +126,37 @@ function reportUpdateState(state, style, details) {
|
||||||
['no-update', 0],
|
['no-update', 0],
|
||||||
['update-problem', 0],
|
['update-problem', 0],
|
||||||
]);
|
]);
|
||||||
switch (state) {
|
if (updated) {
|
||||||
case BG.updater.UPDATED:
|
newClasses.set('can-update', true);
|
||||||
newClasses.set('can-update', true);
|
entry.updatedCode = style;
|
||||||
entry.updatedCode = style;
|
$('.update-note', entry).textContent = '';
|
||||||
$('.update-note', entry).textContent = '';
|
$('#only-updates').classList.remove('hidden');
|
||||||
$('#only-updates').classList.remove('hidden');
|
} else if (!entry.classList.contains('can-update')) {
|
||||||
break;
|
const same = (
|
||||||
case BG.updater.SKIPPED: {
|
error === STATES.SAME_MD5 ||
|
||||||
if (entry.classList.contains('can-update')) {
|
error === STATES.SAME_CODE ||
|
||||||
break;
|
error === STATES.SAME_VERSION
|
||||||
}
|
);
|
||||||
const same = (
|
const edited = error === STATES.EDITED || error === STATES.MAYBE_EDITED;
|
||||||
details === BG.updater.SAME_MD5 ||
|
entry.dataset.error = error;
|
||||||
details === BG.updater.SAME_CODE ||
|
if (!error) {
|
||||||
details === BG.updater.SAME_VERSION
|
error = t('updateCheckFailServerUnreachable') + '\n' + style.updateUrl;
|
||||||
);
|
} else if (typeof error === 'number') {
|
||||||
const edited = details === BG.updater.EDITED || details === BG.updater.MAYBE_EDITED;
|
error = t('updateCheckFailBadResponseCode', [error]) + '\n' + style.updateUrl;
|
||||||
entry.dataset.details = details;
|
} else if (error === STATES.EDITED) {
|
||||||
if (!details) {
|
error = t('updateCheckSkippedLocallyEdited') + '\n' + t('updateCheckManualUpdateHint');
|
||||||
details = t('updateCheckFailServerUnreachable') + '\n' + style.updateUrl;
|
} else if (error === STATES.MAYBE_EDITED) {
|
||||||
} else if (typeof details === 'number') {
|
error = t('updateCheckSkippedMaybeLocallyEdited') + '\n' + t('updateCheckManualUpdateHint');
|
||||||
details = t('updateCheckFailBadResponseCode', [details]) + '\n' + style.updateUrl;
|
}
|
||||||
} else if (details === BG.updater.EDITED) {
|
const message = same ? t('updateCheckSucceededNoUpdate') : error;
|
||||||
details = t('updateCheckSkippedLocallyEdited') + '\n' + t('updateCheckManualUpdateHint');
|
newClasses.set('no-update', true);
|
||||||
} else if (details === BG.updater.MAYBE_EDITED) {
|
newClasses.set('update-problem', !same);
|
||||||
details = t('updateCheckSkippedMaybeLocallyEdited') + '\n' + t('updateCheckManualUpdateHint');
|
$('.update-note', entry).textContent = message;
|
||||||
}
|
$('.check-update', entry).title = newUI.enabled ? message : '';
|
||||||
const message = same ? t('updateCheckSucceededNoUpdate') : details;
|
$('.update', entry).title = t(edited ? 'updateCheckManualUpdateForce' : 'installUpdate');
|
||||||
newClasses.set('no-update', true);
|
if (!document.body.classList.contains('update-in-progress')) {
|
||||||
newClasses.set('update-problem', !same);
|
// this is a single update job so we can decide whether to hide the filter
|
||||||
$('.update-note', entry).textContent = message;
|
renderUpdatesOnlyFilter({show: $('.can-update, .update-problem')});
|
||||||
$('.check-update', entry).title = newUI.enabled ? message : '';
|
|
||||||
$('.update', entry).title = t(edited ? 'updateCheckManualUpdateForce' : 'installUpdate');
|
|
||||||
if (!document.body.classList.contains('update-in-progress')) {
|
|
||||||
// this is a single update job so we can decide whether to hide the filter
|
|
||||||
renderUpdatesOnlyFilter({show: $('.can-update, .update-problem')});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,9 +165,16 @@ function reportUpdateState(state, style, details) {
|
||||||
// 2. remove falsy newClasses
|
// 2. remove falsy newClasses
|
||||||
// 3. keep existing classes otherwise
|
// 3. keep existing classes otherwise
|
||||||
const classes = new Map([...entry.classList.values()].map(cls => [cls, true]));
|
const classes = new Map([...entry.classList.values()].map(cls => [cls, true]));
|
||||||
[...newClasses.entries()].forEach(([cls, newState]) => classes.set(cls, newState));
|
for (const [cls, newState] of newClasses.entries()) {
|
||||||
const className = [...classes.entries()].filter(([, state]) => state).map(([cls]) => cls).join(' ');
|
classes.set(cls, newState);
|
||||||
if (className !== entry.className) entry.className = className;
|
}
|
||||||
|
const className = [...classes.entries()]
|
||||||
|
.map(([cls, state]) => state && cls)
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ');
|
||||||
|
if (className !== entry.className) {
|
||||||
|
entry.className = className;
|
||||||
|
}
|
||||||
|
|
||||||
if (filtersSelector.hide) {
|
if (filtersSelector.hide) {
|
||||||
filterAndAppend({entry});
|
filterAndAppend({entry});
|
||||||
|
@ -200,7 +210,10 @@ function showUpdateHistory(event) {
|
||||||
const log = $create('.update-history-log');
|
const log = $create('.update-history-log');
|
||||||
let logText, scroller, toggler;
|
let logText, scroller, toggler;
|
||||||
let deleted = false;
|
let deleted = false;
|
||||||
BG.chromeLocal.getValue('updateLog').then((lines = []) => {
|
Promise.all([
|
||||||
|
chromeLocal.getValue('updateLog'),
|
||||||
|
API.getUpdaterStates(),
|
||||||
|
]).then(([lines = [], states]) => {
|
||||||
logText = lines.join('\n');
|
logText = lines.join('\n');
|
||||||
messageBox({
|
messageBox({
|
||||||
title: t('updateCheckHistory'),
|
title: t('updateCheckHistory'),
|
||||||
|
@ -227,6 +240,13 @@ function showUpdateHistory(event) {
|
||||||
t('manageOnlyUpdates'),
|
t('manageOnlyUpdates'),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
|
toggler.rxRemoveNOP = new RegExp(
|
||||||
|
'^[^#]*(' +
|
||||||
|
Object.keys(states)
|
||||||
|
.filter(k => k.startsWith('SAME_'))
|
||||||
|
.map(k => states[k])
|
||||||
|
.join('|') +
|
||||||
|
').*\r?\n', 'gm');
|
||||||
toggler.onchange();
|
toggler.onchange();
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
@ -242,26 +262,17 @@ function showUpdateHistory(event) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const scrollRatio = calcScrollRatio();
|
const scrollRatio = calcScrollRatio();
|
||||||
const rxRemoveNOP = this.checked && new RegExp([
|
log.textContent = !this.checked ? logText : logText.replace(this.rxRemoveNOP, '');
|
||||||
'^[^#]*(',
|
|
||||||
Object.keys(BG.updater)
|
|
||||||
.filter(k => k.startsWith('SAME_'))
|
|
||||||
.map(k => stringAsRegExp(BG.updater[k]))
|
|
||||||
.map(rx => rx.source)
|
|
||||||
.join('|'),
|
|
||||||
').*\r?\n',
|
|
||||||
].join(''), 'gm');
|
|
||||||
log.textContent = !this.checked ? logText : logText.replace(rxRemoveNOP, '');
|
|
||||||
if (Math.abs(scrollRatio - calcScrollRatio()) > .1) {
|
if (Math.abs(scrollRatio - calcScrollRatio()) > .1) {
|
||||||
scroller.scrollTop = scrollRatio * scroller.scrollHeight - scroller.clientHeight;
|
scroller.scrollTop = scrollRatio * scroller.scrollHeight - scroller.clientHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function deleteHistory() {
|
function deleteHistory() {
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
BG.chromeLocal.setValue('updateLog', logText.split('\n'));
|
chromeLocal.setValue('updateLog', logText.split('\n'));
|
||||||
setTimeout(scrollToBottom);
|
setTimeout(scrollToBottom);
|
||||||
} else {
|
} else {
|
||||||
BG.chromeLocal.remove('updateLog');
|
chromeLocal.remove('updateLog');
|
||||||
log.textContent = '';
|
log.textContent = '';
|
||||||
}
|
}
|
||||||
deleted = !deleted;
|
deleted = !deleted;
|
||||||
|
|
|
@ -21,17 +21,18 @@
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"js/messaging.js",
|
"js/messaging.js",
|
||||||
"vendor/lz-string/lz-string-unsafe.js",
|
"js/storage-util.js",
|
||||||
"js/color-parser.js",
|
|
||||||
"js/usercss.js",
|
|
||||||
"background/storage.js",
|
"background/storage.js",
|
||||||
"background/usercss-helper.js",
|
|
||||||
"js/prefs.js",
|
"js/prefs.js",
|
||||||
"js/script-loader.js",
|
"js/script-loader.js",
|
||||||
|
"js/color-parser.js",
|
||||||
|
"js/usercss.js",
|
||||||
"background/background.js",
|
"background/background.js",
|
||||||
"vendor/node-semver/semver.js",
|
"background/usercss-helper.js",
|
||||||
"background/style-via-api.js",
|
"background/style-via-api.js",
|
||||||
"background/update.js"
|
"background/search-db.js",
|
||||||
|
"background/update.js",
|
||||||
|
"vendor/node-semver/semver.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"commands": {
|
"commands": {
|
||||||
|
|
|
@ -62,23 +62,26 @@ function checkUpdates() {
|
||||||
let checked = 0;
|
let checked = 0;
|
||||||
let updated = 0;
|
let updated = 0;
|
||||||
const maxWidth = $('#update-progress').parentElement.clientWidth;
|
const maxWidth = $('#update-progress').parentElement.clientWidth;
|
||||||
BG.updater.checkAllStyles({observer});
|
|
||||||
|
|
||||||
function observer(state, value) {
|
chrome.runtime.onConnect.addListener(function onConnect(port) {
|
||||||
switch (state) {
|
if (port.name !== 'updater') return;
|
||||||
case BG.updater.COUNT:
|
port.onMessage.addListener(observer);
|
||||||
total = value;
|
chrome.runtime.onConnect.removeListener(onConnect);
|
||||||
document.body.classList.add('update-in-progress');
|
});
|
||||||
break;
|
|
||||||
case BG.updater.UPDATED:
|
API.updateCheckAll({observe: true});
|
||||||
updated++;
|
|
||||||
// fallthrough
|
function observer(info) {
|
||||||
case BG.updater.SKIPPED:
|
if ('count' in info) {
|
||||||
checked++;
|
total = info.count;
|
||||||
break;
|
document.body.classList.add('update-in-progress');
|
||||||
case BG.updater.DONE:
|
} else if (info.updated) {
|
||||||
document.body.classList.remove('update-in-progress');
|
updated++;
|
||||||
return;
|
checked++;
|
||||||
|
} else if (info.error) {
|
||||||
|
checked++;
|
||||||
|
} else if (info.done) {
|
||||||
|
document.body.classList.remove('update-in-progress');
|
||||||
}
|
}
|
||||||
$('#update-progress').style.width = Math.round(checked / total * maxWidth) + 'px';
|
$('#update-progress').style.width = Math.round(checked / total * maxWidth) + 'px';
|
||||||
$('#updates-installed').dataset.value = updated || '';
|
$('#updates-installed').dataset.value = updated || '';
|
||||||
|
|
|
@ -161,6 +161,8 @@
|
||||||
<script src="popup/popup.js"></script>
|
<script src="popup/popup.js"></script>
|
||||||
<script src="popup/search-results.js"></script>
|
<script src="popup/search-results.js"></script>
|
||||||
<script src="popup/hotkeys.js"></script>
|
<script src="popup/hotkeys.js"></script>
|
||||||
|
<script src="js/script-loader.js" async></script>
|
||||||
|
<script src="js/storage-util.js" async></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body id="stylus-popup">
|
<body id="stylus-popup">
|
||||||
|
|
|
@ -101,11 +101,15 @@ var hotkeys = (() => {
|
||||||
entry = typeof entry === 'string' ? $('#' + entry) : entry;
|
entry = typeof entry === 'string' ? $('#' + entry) : entry;
|
||||||
if (!match && $('.checker', entry).checked !== enable || entry.classList.contains(match)) {
|
if (!match && $('.checker', entry).checked !== enable || entry.classList.contains(match)) {
|
||||||
results.push(entry.id);
|
results.push(entry.id);
|
||||||
task = task.then(() => saveStyleSafe({
|
task = task.then(() => API.saveStyle({
|
||||||
id: entry.styleId,
|
id: entry.styleId,
|
||||||
enabled: enable,
|
enabled: enable,
|
||||||
notify: false,
|
notify: false,
|
||||||
}));
|
})).then(() => {
|
||||||
|
entry.classList.toggle('enabled', enable);
|
||||||
|
entry.classList.toggle('disabled', !enable);
|
||||||
|
$('.checker', entry).checked = enable;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (results.length) {
|
if (results.length) {
|
||||||
|
@ -115,7 +119,7 @@ var hotkeys = (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshAllTabs() {
|
function refreshAllTabs() {
|
||||||
getStylesSafe({matchUrl: location.href, enabled: true, asHash: true})
|
API.getStyles({matchUrl: location.href, enabled: true, asHash: true})
|
||||||
.then(styles => applyOnMessage({method: 'styleReplaceAll', styles}));
|
.then(styles => applyOnMessage({method: 'styleReplaceAll', styles}));
|
||||||
queryTabs().then(tabs =>
|
queryTabs().then(tabs =>
|
||||||
tabs.forEach(tab => (!FIREFOX || tab.width) &&
|
tabs.forEach(tab => (!FIREFOX || tab.width) &&
|
||||||
|
@ -127,11 +131,11 @@ var hotkeys = (() => {
|
||||||
chrome.webNavigation.getAllFrames({tabId}, frames => {
|
chrome.webNavigation.getAllFrames({tabId}, frames => {
|
||||||
frames = frames && frames[0] ? frames : [{frameId: 0}];
|
frames = frames && frames[0] ? frames : [{frameId: 0}];
|
||||||
frames.forEach(({frameId}) =>
|
frames.forEach(({frameId}) =>
|
||||||
getStylesSafe({matchUrl: tab.url, enabled: true, asHash: true}).then(styles => {
|
API.getStyles({matchUrl: tab.url, enabled: true, asHash: true}).then(styles => {
|
||||||
const message = {method: 'styleReplaceAll', tabId, frameId, styles};
|
const message = {method: 'styleReplaceAll', tabId, frameId, styles};
|
||||||
invokeOrPostpone(tab.active, sendMessage, message, ignoreChromeError);
|
invokeOrPostpone(tab.active, sendMessage, message, ignoreChromeError);
|
||||||
if (frameId === 0) {
|
if (frameId === 0) {
|
||||||
setTimeout(BG.updateIcon, 0, tab, styles);
|
setTimeout(API.updateIcon, 0, {tab, styles});
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
ignoreChromeError();
|
ignoreChromeError();
|
||||||
|
|
190
popup/popup.js
190
popup/popup.js
|
@ -15,16 +15,15 @@ getActiveTab().then(tab =>
|
||||||
FIREFOX && tab.url === 'about:blank' && tab.status === 'loading'
|
FIREFOX && tab.url === 'about:blank' && tab.status === 'loading'
|
||||||
? getTabRealURLFirefox(tab)
|
? getTabRealURLFirefox(tab)
|
||||||
: getTabRealURL(tab)
|
: getTabRealURL(tab)
|
||||||
).then(url => {
|
).then(url => Promise.all([
|
||||||
tabURL = URLS.supported(url) ? url : '';
|
(tabURL = URLS.supported(url) ? url : '') &&
|
||||||
Promise.all([
|
API.getStyles({
|
||||||
tabURL && getStylesSafe({matchUrl: tabURL}),
|
matchUrl: tabURL,
|
||||||
onDOMready().then(() => {
|
omitCode: !BG,
|
||||||
initPopup(tabURL);
|
}),
|
||||||
}),
|
onDOMready().then(initPopup),
|
||||||
]).then(([styles]) => {
|
])).then(([styles]) => {
|
||||||
showStyles(styles);
|
showStyles(styles);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener(onRuntimeMessage);
|
chrome.runtime.onMessage.addListener(onRuntimeMessage);
|
||||||
|
@ -33,9 +32,7 @@ function onRuntimeMessage(msg) {
|
||||||
switch (msg.method) {
|
switch (msg.method) {
|
||||||
case 'styleAdded':
|
case 'styleAdded':
|
||||||
case 'styleUpdated':
|
case 'styleUpdated':
|
||||||
// notifyAllTabs sets msg.style's code to null so we have to get the actual style
|
handleUpdate(msg.style);
|
||||||
// because we analyze its code in detectSloppyRegexps
|
|
||||||
handleUpdate(BG.cachedStyles.byId.get(msg.style.id));
|
|
||||||
break;
|
break;
|
||||||
case 'styleDeleted':
|
case 'styleDeleted':
|
||||||
handleDelete(msg.id);
|
handleDelete(msg.id);
|
||||||
|
@ -76,7 +73,7 @@ function toggleSideBorders(state = prefs.get('popup.borders')) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function initPopup(url) {
|
function initPopup() {
|
||||||
installed = $('#installed');
|
installed = $('#installed');
|
||||||
|
|
||||||
setPopupWidth();
|
setPopupWidth();
|
||||||
|
@ -108,7 +105,7 @@ function initPopup(url) {
|
||||||
installed);
|
installed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!url) {
|
if (!tabURL) {
|
||||||
document.body.classList.add('blocked');
|
document.body.classList.add('blocked');
|
||||||
document.body.insertBefore(template.unavailableInfo, document.body.firstChild);
|
document.body.insertBefore(template.unavailableInfo, document.body.firstChild);
|
||||||
return;
|
return;
|
||||||
|
@ -153,10 +150,10 @@ function initPopup(url) {
|
||||||
// For this URL
|
// For this URL
|
||||||
const urlLink = template.writeStyle.cloneNode(true);
|
const urlLink = template.writeStyle.cloneNode(true);
|
||||||
Object.assign(urlLink, {
|
Object.assign(urlLink, {
|
||||||
href: 'edit.html?url-prefix=' + encodeURIComponent(url),
|
href: 'edit.html?url-prefix=' + encodeURIComponent(tabURL),
|
||||||
title: `url-prefix("${url}")`,
|
title: `url-prefix("${tabURL}")`,
|
||||||
textContent: prefs.get('popup.breadcrumbs.usePath')
|
textContent: prefs.get('popup.breadcrumbs.usePath')
|
||||||
? new URL(url).pathname.slice(1)
|
? new URL(tabURL).pathname.slice(1)
|
||||||
// this URL
|
// this URL
|
||||||
: t('writeStyleForURL').replace(/ /g, '\u00a0'),
|
: t('writeStyleForURL').replace(/ /g, '\u00a0'),
|
||||||
onclick: handleEvent.openLink,
|
onclick: handleEvent.openLink,
|
||||||
|
@ -170,7 +167,7 @@ function initPopup(url) {
|
||||||
matchTargets.appendChild(urlLink);
|
matchTargets.appendChild(urlLink);
|
||||||
|
|
||||||
// For domain
|
// For domain
|
||||||
const domains = BG.getDomains(url);
|
const domains = getDomains(tabURL);
|
||||||
for (const domain of domains) {
|
for (const domain of domains) {
|
||||||
const numParts = domain.length - domain.replace(/\./g, '').length + 1;
|
const numParts = domain.length - domain.replace(/\./g, '').length + 1;
|
||||||
// Don't include TLD
|
// Don't include TLD
|
||||||
|
@ -193,6 +190,19 @@ function initPopup(url) {
|
||||||
matchTargets.appendChild(matchTargets.removeChild(matchTargets.firstElementChild));
|
matchTargets.appendChild(matchTargets.removeChild(matchTargets.firstElementChild));
|
||||||
}
|
}
|
||||||
writeStyle.appendChild(matchWrapper);
|
writeStyle.appendChild(matchWrapper);
|
||||||
|
|
||||||
|
function getDomains(url) {
|
||||||
|
let d = /.*?:\/*([^/:]+)|$/.exec(url)[1];
|
||||||
|
if (!d || url.startsWith('file:')) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const domains = [d];
|
||||||
|
while (d.indexOf('.') !== -1) {
|
||||||
|
d = d.substring(d.indexOf('.') + 1);
|
||||||
|
domains.push(d);
|
||||||
|
}
|
||||||
|
return domains;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -213,34 +223,30 @@ function showStyles(styles) {
|
||||||
: a.name.localeCompare(b.name)
|
: a.name.localeCompare(b.name)
|
||||||
));
|
));
|
||||||
|
|
||||||
let postponeDetect = false;
|
|
||||||
const t0 = performance.now();
|
|
||||||
const container = document.createDocumentFragment();
|
const container = document.createDocumentFragment();
|
||||||
for (const style of styles) {
|
styles.forEach(style => createStyleElement({style, container}));
|
||||||
createStyleElement({style, container, postponeDetect});
|
|
||||||
postponeDetect = postponeDetect || performance.now() - t0 > 100;
|
|
||||||
}
|
|
||||||
installed.appendChild(container);
|
installed.appendChild(container);
|
||||||
|
setTimeout(detectSloppyRegexps, 100, styles);
|
||||||
|
|
||||||
getStylesSafe({matchUrl: tabURL, strictRegexp: false})
|
API.getStyles({
|
||||||
.then(unscreenedStyles => {
|
matchUrl: tabURL,
|
||||||
for (const unscreened of unscreenedStyles) {
|
strictRegexp: false,
|
||||||
if (!styles.includes(unscreened)) {
|
omitCode: true,
|
||||||
postponeDetect = postponeDetect || performance.now() - t0 > 100;
|
}).then(unscreenedStyles => {
|
||||||
createStyleElement({
|
for (const style of unscreenedStyles) {
|
||||||
style: Object.assign({appliedSections: [], postponeDetect}, unscreened),
|
if (!styles.find(({id}) => id === style.id)) {
|
||||||
});
|
createStyleElement({style, check: true});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
window.dispatchEvent(new Event('showStyles:done'));
|
}
|
||||||
});
|
window.dispatchEvent(new Event('showStyles:done'));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function createStyleElement({
|
function createStyleElement({
|
||||||
style,
|
style,
|
||||||
|
check = false,
|
||||||
container = installed,
|
container = installed,
|
||||||
postponeDetect,
|
|
||||||
}) {
|
}) {
|
||||||
const entry = template.style.cloneNode(true);
|
const entry = template.style.cloneNode(true);
|
||||||
entry.setAttribute('style-id', style.id);
|
entry.setAttribute('style-id', style.id);
|
||||||
|
@ -294,7 +300,7 @@ function createStyleElement({
|
||||||
$('.delete', entry).onclick = handleEvent.delete;
|
$('.delete', entry).onclick = handleEvent.delete;
|
||||||
$('.configure', entry).onclick = handleEvent.configure;
|
$('.configure', entry).onclick = handleEvent.configure;
|
||||||
|
|
||||||
invokeOrPostpone(!postponeDetect, detectSloppyRegexps, {entry, style});
|
if (check) detectSloppyRegexps([style]);
|
||||||
|
|
||||||
const oldElement = $(ENTRY_ID_PREFIX + style.id);
|
const oldElement = $(ENTRY_ID_PREFIX + style.id);
|
||||||
if (oldElement) {
|
if (oldElement) {
|
||||||
|
@ -316,23 +322,24 @@ Object.assign(handleEvent, {
|
||||||
},
|
},
|
||||||
|
|
||||||
name(event) {
|
name(event) {
|
||||||
this.checkbox.click();
|
this.checkbox.dispatchEvent(new MouseEvent('click'));
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
|
|
||||||
toggle(event) {
|
toggle(event) {
|
||||||
saveStyleSafe({
|
API.saveStyle({
|
||||||
id: handleEvent.getClickedStyleId(event),
|
id: handleEvent.getClickedStyleId(event),
|
||||||
enabled: this.type === 'checkbox' ? this.checked : this.matches('.enable'),
|
enabled: this.matches('.enable') || this.checked,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
delete(event) {
|
delete(event) {
|
||||||
const id = handleEvent.getClickedStyleId(event);
|
const entry = handleEvent.getClickedStyleElement(event);
|
||||||
|
const id = entry.styleId;
|
||||||
const box = $('#confirm');
|
const box = $('#confirm');
|
||||||
box.dataset.display = true;
|
box.dataset.display = true;
|
||||||
box.style.cssText = '';
|
box.style.cssText = '';
|
||||||
$('b', box).textContent = (BG.cachedStyles.byId.get(id) || {}).name;
|
$('b', box).textContent = $('.style-name', entry).textContent;
|
||||||
$('[data-cmd="ok"]', box).focus();
|
$('[data-cmd="ok"]', box).focus();
|
||||||
$('[data-cmd="ok"]', box).onclick = () => confirm(true);
|
$('[data-cmd="ok"]', box).onclick = () => confirm(true);
|
||||||
$('[data-cmd="cancel"]', box).onclick = () => confirm(false);
|
$('[data-cmd="cancel"]', box).onclick = () => confirm(false);
|
||||||
|
@ -350,18 +357,14 @@ Object.assign(handleEvent, {
|
||||||
className: 'lights-on',
|
className: 'lights-on',
|
||||||
onComplete: () => (box.dataset.display = false),
|
onComplete: () => (box.dataset.display = false),
|
||||||
});
|
});
|
||||||
if (ok) {
|
if (ok) API.deleteStyle({id});
|
||||||
deleteStyleSafe({id}).then(() => {
|
|
||||||
handleDelete(id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
configure(event) {
|
configure(event) {
|
||||||
const {styleId, styleIsUsercss} = handleEvent.getClickedStyleElement(event);
|
const {styleId, styleIsUsercss} = handleEvent.getClickedStyleElement(event);
|
||||||
if (styleIsUsercss) {
|
if (styleIsUsercss) {
|
||||||
getStylesSafe({id: styleId}).then(([style]) => {
|
API.getStyles({id: styleId}).then(([style]) => {
|
||||||
hotkeys.setState(false);
|
hotkeys.setState(false);
|
||||||
configDialog(deepCopy(style)).then(() => {
|
configDialog(deepCopy(style)).then(() => {
|
||||||
hotkeys.setState(true);
|
hotkeys.setState(true);
|
||||||
|
@ -456,15 +459,22 @@ Object.assign(handleEvent, {
|
||||||
|
|
||||||
function handleUpdate(style) {
|
function handleUpdate(style) {
|
||||||
if ($(ENTRY_ID_PREFIX + style.id)) {
|
if ($(ENTRY_ID_PREFIX + style.id)) {
|
||||||
createStyleElement({style});
|
createStyleElement({style, check: true});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!tabURL) return;
|
||||||
// Add an entry when a new style for the current url is installed
|
// Add an entry when a new style for the current url is installed
|
||||||
if (tabURL && BG.getApplicableSections({style, matchUrl: tabURL, stopOnFirst: true}).length) {
|
API.getStyles({
|
||||||
document.body.classList.remove('blocked');
|
matchUrl: tabURL,
|
||||||
$$.remove('.blocked-info, #no-styles');
|
stopOnFirst: true,
|
||||||
createStyleElement({style});
|
omitCode: true,
|
||||||
}
|
}).then(([style]) => {
|
||||||
|
if (style) {
|
||||||
|
document.body.classList.remove('blocked');
|
||||||
|
$$.remove('.blocked-info, #no-styles');
|
||||||
|
createStyleElement({style, check: true});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -476,58 +486,28 @@ function handleDelete(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
function detectSloppyRegexps(styles) {
|
||||||
According to CSS4 @document specification the entire URL must match.
|
API.detectSloppyRegexps({
|
||||||
Stylish-for-Chrome implemented it incorrectly since the very beginning.
|
matchUrl: tabURL,
|
||||||
We'll detect styles that abuse the bug by finding the sections that
|
ids: styles.map(({id}) => id),
|
||||||
would have been applied by Stylish but not by us as we follow the spec.
|
}).then(results => {
|
||||||
Additionally we'll check for invalid regexps.
|
for (const {id, applied, skipped, hasInvalidRegexps} of results) {
|
||||||
*/
|
const entry = $(ENTRY_ID_PREFIX + id);
|
||||||
function detectSloppyRegexps({entry, style}) {
|
if (!entry) continue;
|
||||||
// make sure all regexps are compiled
|
if (!applied) {
|
||||||
const rxCache = BG.cachedStyles.regexps;
|
entry.classList.add('not-applied');
|
||||||
let hasRegExp = false;
|
$('.style-name', entry).title = t('styleNotAppliedRegexpProblemTooltip');
|
||||||
for (const section of style.sections) {
|
}
|
||||||
for (const regexp of section.regexps) {
|
if (skipped || hasInvalidRegexps) {
|
||||||
hasRegExp = true;
|
entry.classList.toggle('regexp-partial', Boolean(skipped));
|
||||||
for (let pass = 1; pass <= 2; pass++) {
|
entry.classList.toggle('regexp-invalid', Boolean(hasInvalidRegexps));
|
||||||
const cacheKey = pass === 1 ? regexp : BG.SLOPPY_REGEXP_PREFIX + regexp;
|
const indicator = template.regexpProblemIndicator.cloneNode(true);
|
||||||
if (!rxCache.has(cacheKey)) {
|
indicator.appendChild(document.createTextNode(entry.skipped || '!'));
|
||||||
// according to CSS4 @document specification the entire URL must match
|
indicator.onclick = handleEvent.indicator;
|
||||||
const anchored = pass === 1 ? '^(?:' + regexp + ')$' : '^' + regexp + '$';
|
$('.main-controls', entry).appendChild(indicator);
|
||||||
// create in the bg context to avoid leaking of "dead objects"
|
|
||||||
const rx = BG.tryRegExp(anchored);
|
|
||||||
rxCache.set(cacheKey, rx || false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
if (!hasRegExp) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const {
|
|
||||||
appliedSections =
|
|
||||||
BG.getApplicableSections({style, matchUrl: tabURL}),
|
|
||||||
wannabeSections =
|
|
||||||
BG.getApplicableSections({style, matchUrl: tabURL, strictRegexp: false}),
|
|
||||||
} = style;
|
|
||||||
|
|
||||||
entry.hasInvalidRegexps = wannabeSections.some(section =>
|
|
||||||
section.regexps.some(rx => !rxCache.has(rx)));
|
|
||||||
entry.sectionsSkipped = wannabeSections.length - appliedSections.length;
|
|
||||||
|
|
||||||
if (!appliedSections.length) {
|
|
||||||
entry.classList.add('not-applied');
|
|
||||||
$('.style-name', entry).title = t('styleNotAppliedRegexpProblemTooltip');
|
|
||||||
}
|
|
||||||
if (entry.sectionsSkipped || entry.hasInvalidRegexps) {
|
|
||||||
entry.classList.toggle('regexp-partial', entry.sectionsSkipped);
|
|
||||||
entry.classList.toggle('regexp-invalid', entry.hasInvalidRegexps);
|
|
||||||
const indicator = template.regexpProblemIndicator.cloneNode(true);
|
|
||||||
indicator.appendChild(document.createTextNode(entry.sectionsSkipped || '!'));
|
|
||||||
indicator.onclick = handleEvent.indicator;
|
|
||||||
$('.main-controls', entry).appendChild(indicator);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -131,7 +131,7 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
if (result) {
|
if (result) {
|
||||||
result.installed = false;
|
result.installed = false;
|
||||||
result.installedStyleId = -1;
|
result.installedStyleId = -1;
|
||||||
BG.clearTimeout(result.pingbackTimer);
|
(BG || window).clearTimeout(result.pingbackTimer);
|
||||||
renderActionButtons($('#' + RESULT_ID_PREFIX + result.id));
|
renderActionButtons($('#' + RESULT_ID_PREFIX + result.id));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -280,7 +280,7 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const md5Url = UPDATE_URL.replace('%', result.id);
|
const md5Url = UPDATE_URL.replace('%', result.id);
|
||||||
getStylesSafe({md5Url}).then(([installedStyle]) => {
|
API.getStyles({md5Url}).then(([installedStyle]) => {
|
||||||
if (installedStyle) {
|
if (installedStyle) {
|
||||||
totalResults = Math.max(0, totalResults - 1);
|
totalResults = Math.max(0, totalResults - 1);
|
||||||
} else {
|
} else {
|
||||||
|
@ -522,7 +522,7 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const entry = this.closest('.search-result');
|
const entry = this.closest('.search-result');
|
||||||
saveScrollPosition(entry);
|
saveScrollPosition(entry);
|
||||||
deleteStyleSafe({id: entry._result.installedStyleId})
|
API.deleteStyle({id: entry._result.installedStyleId})
|
||||||
.then(restoreScrollPosition);
|
.then(restoreScrollPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,11 +550,11 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
style.updateUrl += settings.length ? '?' : '';
|
style.updateUrl += settings.length ? '?' : '';
|
||||||
// show a 'style installed' tooltip in the manager
|
// show a 'style installed' tooltip in the manager
|
||||||
style.reason = 'install';
|
style.reason = 'install';
|
||||||
return saveStyleSafe(style);
|
return API.saveStyle(style);
|
||||||
})
|
})
|
||||||
.catch(reason => {
|
.catch(reason => {
|
||||||
const usoId = result.id;
|
const usoId = result.id;
|
||||||
console.debug('install:saveStyleSafe(usoID:', usoId, ') => [ERROR]: ', reason);
|
console.debug('install:saveStyle(usoID:', usoId, ') => [ERROR]: ', reason);
|
||||||
error('Error while downloading usoID:' + usoId + '\nReason: ' + reason);
|
error('Error while downloading usoID:' + usoId + '\nReason: ' + reason);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -574,7 +574,8 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function pingback(result) {
|
function pingback(result) {
|
||||||
result.pingbackTimer = BG.setTimeout(BG.download, PINGBACK_DELAY,
|
const wnd = BG || window;
|
||||||
|
result.pingbackTimer = wnd.setTimeout(wnd.download, PINGBACK_DELAY,
|
||||||
BASE_URL + '/styles/install/' + result.id + '?source=stylish-ch');
|
BASE_URL + '/styles/install/' + result.id + '?source=stylish-ch');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -721,9 +722,10 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
|
|
||||||
function readCache(id) {
|
function readCache(id) {
|
||||||
const key = CACHE_PREFIX + id;
|
const key = CACHE_PREFIX + id;
|
||||||
return BG.chromeLocal.getValue(key).then(item => {
|
return chromeLocal.getValue(key).then(item => {
|
||||||
if (!cacheItemExpired(item)) {
|
if (!cacheItemExpired(item)) {
|
||||||
return tryJSONparse(BG.LZString.decompressFromUTF16(item.payload));
|
return chromeLocal.loadLZStringScript().then(() =>
|
||||||
|
tryJSONparse(LZString.decompressFromUTF16(item.payload)));
|
||||||
} else if (item) {
|
} else if (item) {
|
||||||
chrome.storage.local.remove(key);
|
chrome.storage.local.remove(key);
|
||||||
}
|
}
|
||||||
|
@ -741,10 +743,11 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
return data;
|
return data;
|
||||||
} else {
|
} else {
|
||||||
debounce(cleanupCache, CACHE_CLEANUP_THROTTLE);
|
debounce(cleanupCache, CACHE_CLEANUP_THROTTLE);
|
||||||
return BG.chromeLocal.setValue(CACHE_PREFIX + data.id, {
|
return chromeLocal.loadLZStringScript().then(() =>
|
||||||
payload: BG.LZString.compressToUTF16(JSON.stringify(data)),
|
chromeLocal.setValue(CACHE_PREFIX + data.id, {
|
||||||
date: Date.now(),
|
payload: LZString.compressToUTF16(JSON.stringify(data)),
|
||||||
}).then(() => data);
|
date: Date.now(),
|
||||||
|
})).then(() => data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user