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) { | function onRuntimeMessage(msg, sender, sendResponse) { | ||||||
|   const sendResponse = data => { |   const fn = API_METHODS[msg.method]; | ||||||
|     // wrap Error object instance as {__ERROR__: message} - will be unwrapped in sendMessage
 |   if (!fn) return; | ||||||
|     if (data instanceof Error) { | 
 | ||||||
|       data = {__ERROR__: data.message}; |   // wrap 'Error' object instance as {__ERROR__: message},
 | ||||||
|     } |   // which will be unwrapped by sendMessage,
 | ||||||
|     // prevent browser exception bug on sending a response to a closed tab
 |   // and prevent exceptions on sending to a closed tab
 | ||||||
|     tryCatch(sendResponseInternal, data); |   const respond = data => | ||||||
|   }; |     tryCatch(sendResponse, | ||||||
|   switch (request.method) { |       data instanceof Error ? {__ERROR__: data.message} : data); | ||||||
|     case 'getStyles': | 
 | ||||||
|       getStyles(request).then(sendResponse); |   const result = fn(msg, sender, respond); | ||||||
|  |   if (result instanceof Promise) { | ||||||
|  |     result | ||||||
|  |       .catch(e => ({__ERROR__: e instanceof Error ? e.message : e})) | ||||||
|  |       .then(respond); | ||||||
|     return KEEP_CHANNEL_OPEN; |     return KEEP_CHANNEL_OPEN; | ||||||
| 
 |   } else if (result === KEEP_CHANNEL_OPEN) { | ||||||
|     case 'saveStyle': |  | ||||||
|       saveStyle(request).then(sendResponse); |  | ||||||
|     return KEEP_CHANNEL_OPEN; |     return KEEP_CHANNEL_OPEN; | ||||||
| 
 |   } else if (result !== undefined) { | ||||||
|     case 'saveUsercss': |     respond(result); | ||||||
|       usercssHelper.save(request, true).then(sendResponse); |  | ||||||
|       return KEEP_CHANNEL_OPEN; |  | ||||||
| 
 |  | ||||||
|     case 'buildUsercss': |  | ||||||
|       usercssHelper.build(request, true).then(sendResponse); |  | ||||||
|       return KEEP_CHANNEL_OPEN; |  | ||||||
| 
 |  | ||||||
|     case 'healthCheck': |  | ||||||
|       dbExec() |  | ||||||
|         .then(() => sendResponse(true)) |  | ||||||
|         .catch(() => sendResponse(false)); |  | ||||||
|       return KEEP_CHANNEL_OPEN; |  | ||||||
| 
 |  | ||||||
|     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,15 +227,15 @@ 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({ | ||||||
|   return filterStylesInternal({ |  | ||||||
|       enabled, |       enabled, | ||||||
|       id, |       id, | ||||||
|       matchUrl, |       matchUrl, | ||||||
|  | @ -292,6 +245,16 @@ function filterStyles({ | ||||||
|       blankHash, |       blankHash, | ||||||
|       cacheKey, |       cacheKey, | ||||||
|     }); |     }); | ||||||
|  |   } | ||||||
|  |   if (!omitCode) return styles; | ||||||
|  |   if (!asHash) return styles.map(getStyleWithNoCode); | ||||||
|  |   for (const id in styles) { | ||||||
|  |     const style = styles[id]; | ||||||
|  |     if (style && style.sections) { | ||||||
|  |       styles[id] = getStyleWithNoCode(style); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return styles; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -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,15 +1,17 @@ | ||||||
| /* 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', | ||||||
|  | @ -20,28 +22,53 @@ var updater = { | ||||||
|     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() { |  | ||||||
|     localStorage.lastUpdateTime = updater.lastUpdateTime = Date.now(); |  | ||||||
|     updater.schedule(); |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   log: (() => { |  | ||||||
|     let queue = []; |  | ||||||
|     let lastWriteTime = 0; |  | ||||||
|     return text => { |  | ||||||
|       queue.push({text, time: new Date().toLocaleString()}); |  | ||||||
|       debounce(flushQueue, text && updater.checkAllStyles.running ? 1000 : 0); |  | ||||||
|     }; |  | ||||||
|     function flushQueue() { |  | ||||||
|       chromeLocal.getValue('updateLog').then((lines = []) => { |  | ||||||
|         const time = Date.now() - lastWriteTime > 11e3 ? queue[0].time + ' ' : ''; |  | ||||||
|         if (!queue[0].text) { |  | ||||||
|           queue.shift(); |  | ||||||
|           if (lines[lines.length - 1]) { |  | ||||||
|             lines.push(''); |  | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   function resetInterval() { | ||||||
|  |     localStorage.lastUpdateTime = lastUpdateTime = Date.now(); | ||||||
|  |     schedule(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function log(text) { | ||||||
|  |     logQueue.push({text, time: new Date().toLocaleString()}); | ||||||
|  |     debounce(flushQueue, text && checkingAll ? 1000 : 0); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function flushQueue(stored) { | ||||||
|  |     if (!stored) { | ||||||
|  |       chrome.storage.local.get('updateLog', flushQueue); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     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.splice(0, lines.length - 1000); | ||||||
|         lines.push(time + queue[0].text); |     lines.push(time + (logQueue[0] && logQueue[0].text || '')); | ||||||
|         lines.push(...queue.slice(1).map(item => item.text)); |     lines.push(...logQueue.slice(1).map(item => item.text)); | ||||||
|         chromeLocal.setValue('updateLog', lines); |  | ||||||
|         lastWriteTime = Date.now(); |  | ||||||
|         queue = []; |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
|   })(), |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| 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> | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								edit/edit.js
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								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]) => { | ||||||
|  |           if (isUsercss(style)) { | ||||||
|  |             editor.replaceStyle(style, request.codeIsUpdated); | ||||||
|           } else { |           } else { | ||||||
|           initWithSectionStyle(request); |             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; | ||||||
|  |  | ||||||
							
								
								
									
										177
									
								
								js/messaging.js
									
									
									
									
									
								
							
							
						
						
									
										177
									
								
								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; |     return copy; | ||||||
|   } |   } | ||||||
|  |   const copy = {}; | ||||||
|  |   const hasOwnProperty = Object.prototype.hasOwnProperty; | ||||||
|   for (const k in obj) { |   for (const k in obj) { | ||||||
|       const value = obj[k]; |     if (!hasOwnProperty.call(obj, k)) continue; | ||||||
|       if (k in target && typeof value === 'object' && value !== null) { |     const v = obj[k]; | ||||||
|         deepMerge(target[k], value); |     copy[k] = !v || typeof v !== 'object' ? v : deepCopy(v); | ||||||
|       } else { |  | ||||||
|         target[k] = deepCopy(value); |  | ||||||
|   } |   } | ||||||
|     } |   return copy; | ||||||
|   } |  | ||||||
|   return target; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -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}`; | ||||||
|  |  | ||||||
							
								
								
									
										67
									
								
								js/prefs.js
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								js/prefs.js
									
									
									
									
									
								
							|  | @ -148,18 +148,14 @@ 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) { |  | ||||||
|           BG.prefs.set(key, BG.deepCopy(value), {broadcast, sync}); |  | ||||||
|         } else { |  | ||||||
|         localStorage[key] = typeof defaults[key] === 'object' |         localStorage[key] = typeof defaults[key] === 'object' | ||||||
|           ? JSON.stringify(value) |           ? JSON.stringify(value) | ||||||
|           : value; |           : value; | ||||||
|           if (broadcast && hasChanged) { |       } | ||||||
|  |       if (!fromBroadcast && broadcast && hasChanged) { | ||||||
|         this.broadcast(key, value, {sync}); |         this.broadcast(key, value, {sync}); | ||||||
|       } |       } | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       if (hasChanged) { |       if (hasChanged) { | ||||||
|         const specific = onChange.specific.get(key); |         const specific = onChange.specific.get(key); | ||||||
|         if (typeof specific === 'function') { |         if (typeof specific === 'function') { | ||||||
|  | @ -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,9 +220,20 @@ 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 => { | ||||||
|  |         const props = {}; | ||||||
|  |         for (const id in prefs) { | ||||||
|  |           const value = prefs[id]; | ||||||
|  |           values[id] = value; | ||||||
|  |           props[id] = {value: deepCopy(value)}; | ||||||
|  |         } | ||||||
|  |         Object.defineProperties(this.readOnlyValues, props); | ||||||
|  |       }); | ||||||
|  |     // Unlike chrome.storage or messaging, HTML5 localStorage is synchronous and always ready,
 | ||||||
|  |     // so we'll mirror the prefs to avoid using the wrong defaults during the startup phase
 | ||||||
|  |     const importFromLocalStorage = () => { | ||||||
|       for (const key in defaults) { |       for (const key in defaults) { | ||||||
|         const defaultValue = defaults[key]; |         const defaultValue = defaults[key]; | ||||||
|         let value = localStorage[key]; |         let value = localStorage[key]; | ||||||
|  | @ -247,6 +252,7 @@ var prefs = new function Prefs() { | ||||||
|         } else if (FIREFOX_NO_DOM_STORAGE && BG) { |         } else if (FIREFOX_NO_DOM_STORAGE && BG) { | ||||||
|           value = BG.localStorage[key]; |           value = BG.localStorage[key]; | ||||||
|           value = value === undefined ? defaultValue : value; |           value = value === undefined ? defaultValue : value; | ||||||
|  |           localStorage[key] = value; | ||||||
|         } else { |         } else { | ||||||
|           value = defaultValue; |           value = defaultValue; | ||||||
|         } |         } | ||||||
|  | @ -258,31 +264,20 @@ var prefs = new function Prefs() { | ||||||
|           defineReadonlyProperty(this.readOnlyValues, key, value); |           defineReadonlyProperty(this.readOnlyValues, key, value); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| 
 |       return Promise.resolve(); | ||||||
|   if (!BG || BG === window) { |  | ||||||
|     affectsIcon.forEach(key => this.broadcast(key, values[key], {sync: false})); |  | ||||||
| 
 |  | ||||||
|     const importFromSync = (synced = {}) => { |  | ||||||
|       for (const key in defaults) { |  | ||||||
|         if (key in synced) { |  | ||||||
|           this.set(key, synced[key], {sync: false}); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }; |     }; | ||||||
| 
 |     (FIREFOX_NO_DOM_STORAGE && !BG ? importFromBG() : importFromLocalStorage()).then(() => { | ||||||
|     getSync().get('settings', ({settings} = {}) => importFromSync(settings)); |       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) => { |       chrome.storage.onChanged.addListener((changes, area) => { | ||||||
|         if (area === 'sync' && 'settings' in changes) { |         if (area === 'sync' && 'settings' in changes) { | ||||||
|         const synced = changes.settings.newValue; |           importFromSync.call(this, changes.settings.newValue); | ||||||
|         if (synced) { |  | ||||||
|           importFromSync(synced); |  | ||||||
|         } else { |  | ||||||
|           // user manually deleted our settings, we'll recreate them
 |  | ||||||
|           getSync().set({'settings': values}); |  | ||||||
|         } |  | ||||||
|         } |         } | ||||||
|       }); |       }); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // any access to chrome API takes time due to initialization of bindings
 |   // any access to chrome API takes time due to initialization of bindings
 | ||||||
|  | @ -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,45 +269,26 @@ 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, | ||||||
|  |     ids: [...entries].map(el => el.styleId), | ||||||
|  |   }).then(ids => { | ||||||
|  |     ids = new Set(ids); | ||||||
|     let needsRefilter = false; |     let needsRefilter = false; | ||||||
|     for (const entry of entries) { |     for (const entry of entries) { | ||||||
|     let isMatching = !query || words && !words.length; |       const isMatching = ids.has(entry.styleId); | ||||||
|     if (!isMatching) { |  | ||||||
|       const style = urlMode ? siteStyleIds.has(entry.styleId) : |  | ||||||
|         BG.cachedStyles.byId.get(entry.styleId) || {}; |  | ||||||
|       isMatching = Boolean(style && ( |  | ||||||
|         urlMode || |  | ||||||
|         isMatchingText(style.name) || |  | ||||||
|         style.url && isMatchingText(style.url) || |  | ||||||
|         style.sourceCode && isMatchingText(style.sourceCode) || |  | ||||||
|         isMatchingStyle(style))); |  | ||||||
|     } |  | ||||||
|       if (entry.classList.contains('not-matching') !== !isMatching) { |       if (entry.classList.contains('not-matching') !== !isMatching) { | ||||||
|         entry.classList.toggle('not-matching', !isMatching); |         entry.classList.toggle('not-matching', !isMatching); | ||||||
|         needsRefilter = true; |         needsRefilter = true; | ||||||
|  | @ -320,40 +297,6 @@ function searchStyles({immediately, container}) { | ||||||
|     if (needsRefilter && !container) { |     if (needsRefilter && !container) { | ||||||
|       filterOnChange({forceRefilter: true}); |       filterOnChange({forceRefilter: true}); | ||||||
|     } |     } | ||||||
|   return; |     return container; | ||||||
| 
 |   }); | ||||||
|   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; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   function isMatchingText(text) { |  | ||||||
|     if (rx) { |  | ||||||
|       return rx.test(text); |  | ||||||
|     } |  | ||||||
|     for (let pass = 1; pass <= 2; pass++) { |  | ||||||
|       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: | 
 | ||||||
|  |   function observer(info) { | ||||||
|  |     if ('count' in info) { | ||||||
|  |       total = info.count; | ||||||
|  |     } | ||||||
|  |     if (info.updated) { | ||||||
|       if (++updated === 1) { |       if (++updated === 1) { | ||||||
|           $('#apply-all-updates').disabled = true; |         btnApply.disabled = true; | ||||||
|           $('#apply-all-updates').classList.remove('hidden'); |         btnApply.classList.remove('hidden'); | ||||||
|       } |       } | ||||||
|         $('#apply-all-updates').dataset.value = updated; |       btnApply.dataset.value = updated; | ||||||
|         // fallthrough
 |     } | ||||||
|       case BG.updater.SKIPPED: |     if (info.updated || info.error) { | ||||||
|       checked++; |       checked++; | ||||||
|         if (details === BG.updater.EDITED || details === BG.updater.MAYBE_EDITED) { |       skippedEdited += [info.STATES.EDITED, info.STATES.MAYBE_EDITED].includes(info.error); | ||||||
|           skippedEdited++; |       reportUpdateState(info); | ||||||
|         } |  | ||||||
|         reportUpdateState(state, value, details); |  | ||||||
|         break; |  | ||||||
|     } |     } | ||||||
|     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,34 +126,29 @@ 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'); | ||||||
|       break; |   } else if (!entry.classList.contains('can-update')) { | ||||||
|     case BG.updater.SKIPPED: { |  | ||||||
|       if (entry.classList.contains('can-update')) { |  | ||||||
|         break; |  | ||||||
|       } |  | ||||||
|     const same = ( |     const same = ( | ||||||
|         details === BG.updater.SAME_MD5 || |       error === STATES.SAME_MD5 || | ||||||
|         details === BG.updater.SAME_CODE || |       error === STATES.SAME_CODE || | ||||||
|         details === BG.updater.SAME_VERSION |       error === STATES.SAME_VERSION | ||||||
|     ); |     ); | ||||||
|       const edited = details === BG.updater.EDITED || details === BG.updater.MAYBE_EDITED; |     const edited = error === STATES.EDITED || error === STATES.MAYBE_EDITED; | ||||||
|       entry.dataset.details = details; |     entry.dataset.error = error; | ||||||
|       if (!details) { |     if (!error) { | ||||||
|         details = t('updateCheckFailServerUnreachable') + '\n' + style.updateUrl; |       error = t('updateCheckFailServerUnreachable') + '\n' + style.updateUrl; | ||||||
|       } else if (typeof details === 'number') { |     } else if (typeof error === 'number') { | ||||||
|         details = t('updateCheckFailBadResponseCode', [details]) + '\n' + style.updateUrl; |       error = t('updateCheckFailBadResponseCode', [error]) + '\n' + style.updateUrl; | ||||||
|       } else if (details === BG.updater.EDITED) { |     } else if (error === STATES.EDITED) { | ||||||
|         details = t('updateCheckSkippedLocallyEdited') + '\n' + t('updateCheckManualUpdateHint'); |       error = t('updateCheckSkippedLocallyEdited') + '\n' + t('updateCheckManualUpdateHint'); | ||||||
|       } else if (details === BG.updater.MAYBE_EDITED) { |     } else if (error === STATES.MAYBE_EDITED) { | ||||||
|         details = t('updateCheckSkippedMaybeLocallyEdited') + '\n' + t('updateCheckManualUpdateHint'); |       error = t('updateCheckSkippedMaybeLocallyEdited') + '\n' + t('updateCheckManualUpdateHint'); | ||||||
|     } |     } | ||||||
|       const message = same ? t('updateCheckSucceededNoUpdate') : details; |     const message = same ? t('updateCheckSucceededNoUpdate') : error; | ||||||
|     newClasses.set('no-update', true); |     newClasses.set('no-update', true); | ||||||
|     newClasses.set('update-problem', !same); |     newClasses.set('update-problem', !same); | ||||||
|     $('.update-note', entry).textContent = message; |     $('.update-note', entry).textContent = message; | ||||||
|  | @ -155,16 +159,22 @@ function reportUpdateState(state, style, details) { | ||||||
|       renderUpdatesOnlyFilter({show: $('.can-update, .update-problem')}); |       renderUpdatesOnlyFilter({show: $('.can-update, .update-problem')}); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   // construct a new className:
 |   // construct a new className:
 | ||||||
|   // 1. add all truthy newClasses
 |   // 1. add all truthy newClasses
 | ||||||
|   // 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); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   API.updateCheckAll({observe: true}); | ||||||
|  | 
 | ||||||
|  |   function observer(info) { | ||||||
|  |     if ('count' in info) { | ||||||
|  |       total = info.count; | ||||||
|       document.body.classList.add('update-in-progress'); |       document.body.classList.add('update-in-progress'); | ||||||
|         break; |     } else if (info.updated) { | ||||||
|       case BG.updater.UPDATED: |  | ||||||
|       updated++; |       updated++; | ||||||
|         // fallthrough
 |  | ||||||
|       case BG.updater.SKIPPED: |  | ||||||
|       checked++; |       checked++; | ||||||
|         break; |     } else if (info.error) { | ||||||
|       case BG.updater.DONE: |       checked++; | ||||||
|  |     } else if (info.done) { | ||||||
|       document.body.classList.remove('update-in-progress'); |       document.body.classList.remove('update-in-progress'); | ||||||
|         return; |  | ||||||
|     } |     } | ||||||
|     $('#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(); | ||||||
|  |  | ||||||
							
								
								
									
										164
									
								
								popup/popup.js
									
									
									
									
									
								
							
							
						
						
									
										164
									
								
								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); |  | ||||||
|   }), |   }), | ||||||
|   ]).then(([styles]) => { |   onDOMready().then(initPopup), | ||||||
|  | ])).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,23 +223,19 @@ 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')); | ||||||
|  | @ -239,8 +245,8 @@ function showStyles(styles) { | ||||||
| 
 | 
 | ||||||
| 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({ | ||||||
|  |     matchUrl: tabURL, | ||||||
|  |     stopOnFirst: true, | ||||||
|  |     omitCode: true, | ||||||
|  |   }).then(([style]) => { | ||||||
|  |     if (style) { | ||||||
|       document.body.classList.remove('blocked'); |       document.body.classList.remove('blocked'); | ||||||
|       $$.remove('.blocked-info, #no-styles'); |       $$.remove('.blocked-info, #no-styles'); | ||||||
|     createStyleElement({style}); |       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; |  | ||||||
|   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 : BG.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 = 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'); |         entry.classList.add('not-applied'); | ||||||
|         $('.style-name', entry).title = t('styleNotAppliedRegexpProblemTooltip'); |         $('.style-name', entry).title = t('styleNotAppliedRegexpProblemTooltip'); | ||||||
|       } |       } | ||||||
|   if (entry.sectionsSkipped || entry.hasInvalidRegexps) { |       if (skipped || hasInvalidRegexps) { | ||||||
|     entry.classList.toggle('regexp-partial', entry.sectionsSkipped); |         entry.classList.toggle('regexp-partial', Boolean(skipped)); | ||||||
|     entry.classList.toggle('regexp-invalid', entry.hasInvalidRegexps); |         entry.classList.toggle('regexp-invalid', Boolean(hasInvalidRegexps)); | ||||||
|         const indicator = template.regexpProblemIndicator.cloneNode(true); |         const indicator = template.regexpProblemIndicator.cloneNode(true); | ||||||
|     indicator.appendChild(document.createTextNode(entry.sectionsSkipped || '!')); |         indicator.appendChild(document.createTextNode(entry.skipped || '!')); | ||||||
|         indicator.onclick = handleEvent.indicator; |         indicator.onclick = handleEvent.indicator; | ||||||
|         $('.main-controls', entry).appendChild(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, { | ||||||
|  |           payload: LZString.compressToUTF16(JSON.stringify(data)), | ||||||
|           date: Date.now(), |           date: Date.now(), | ||||||
|       }).then(() => data); |         })).then(() => data); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user