Previously, when a cache was invalidated and every tab/iframe issued a getStyles request, we previous needlessly accessed IndexedDB for each of these requests. It happened because 1) the global cachedStyles was created only at the end of the async DB-reading, 2) and each style record is retrieved asynchronously so the single threaded JS engine interleaved all these operations. It could easily span a few seconds when many tabs are open and you have like 100 styles. Now, in getStyles: all requests issued while cachedStyles is being populated are queued and invoked at the end. Now, in filterStyles: all requests are cached using the request's options combined in a string as a key. It also helps on each navigation because we monitor page loading process at different stages: before, when committed, history traversal, requesting applicable styles by a content script. Icon badge update also may issue a copy of the just issued request by one of the navigation listeners. Now, the caches are invalidated smartly: style add/update/delete/toggle only purges filtering cache, and modifies style cache in-place without re-reading the entire IndexedDB. Now, code:false mode for manage page that only needs style meta. It reduces the transferred message size 10-100 times thus reducing the overhead caused by to internal JSON-fication in the extensions API. Also fast&direct getStylesSafe for own pages; code cosmetics
		
			
				
	
	
		
			152 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			152 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // keep message channel open for sendResponse in chrome.runtime.onMessage listener
 | |
| const KEEP_CHANNEL_OPEN = true;
 | |
| const OWN_ORIGIN = chrome.runtime.getURL('');
 | |
| 
 | |
| function notifyAllTabs(request) {
 | |
| 	// list all tabs including chrome-extension:// which can be ours
 | |
| 	if (request.codeIsUpdated === false && request.style) {
 | |
| 		request = Object.assign({}, request, {
 | |
| 			style: getStyleWithNoCode(request.style)
 | |
|     });
 | |
| 	}
 | |
| 	chrome.tabs.query({}, tabs => {
 | |
| 		for (let tab of tabs) {
 | |
| 			chrome.tabs.sendMessage(tab.id, request);
 | |
| 			updateIcon(tab);
 | |
| 		}
 | |
| 	});
 | |
| 	// notify all open popups
 | |
| 	const reqPopup = Object.assign({}, request, {method: 'updatePopup', reason: request.method});
 | |
| 	chrome.runtime.sendMessage(reqPopup);
 | |
| 	// notify self: the message no longer is sent to the origin in new Chrome
 | |
| 	if (typeof applyOnMessage !== 'undefined') {
 | |
| 		applyOnMessage(reqPopup);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| function refreshAllTabs() {
 | |
| 	return new Promise(resolve => {
 | |
| 		// list all tabs including chrome-extension:// which can be ours
 | |
| 		chrome.tabs.query({}, tabs => {
 | |
| 			const lastTab = tabs[tabs.length - 1];
 | |
| 			for (let tab of tabs) {
 | |
| 				getStyles({matchUrl: tab.url, enabled: true, asHash: true}, styles => {
 | |
| 					const message = {method: 'styleReplaceAll', styles};
 | |
| 					if (tab.url == location.href && typeof applyOnMessage !== 'undefined') {
 | |
| 						applyOnMessage(message);
 | |
| 					} else {
 | |
| 						chrome.tabs.sendMessage(tab.id, message);
 | |
| 					}
 | |
| 					updateIcon(tab, styles);
 | |
| 					if (tab == lastTab) {
 | |
| 						resolve();
 | |
| 					}
 | |
| 				});
 | |
| 			}
 | |
| 		});
 | |
| 	});
 | |
| }
 | |
| 
 | |
| function updateIcon(tab, styles) {
 | |
| 	// while NTP is still loading only process the request for its main frame with a real url
 | |
| 	// (but when it's loaded we should process style toggle requests from popups, for example)
 | |
| 	if (tab.url == 'chrome://newtab/' && tab.status != 'complete') {
 | |
| 		return;
 | |
| 	}
 | |
| 	if (styles) {
 | |
| 		// check for not-yet-existing tabs e.g. omnibox instant search
 | |
| 		chrome.tabs.get(tab.id, () => {
 | |
| 			if (!chrome.runtime.lastError) {
 | |
| 				stylesReceived(styles);
 | |
| 			}
 | |
| 		});
 | |
| 		return;
 | |
| 	}
 | |
| 	getTabRealURL(tab, url => {
 | |
| 		// if we have access to this, call directly
 | |
| 		// (Chrome no longer sends messages to the page itself)
 | |
| 		const options = {method: 'getStyles', matchUrl: url, enabled: true, asHash: true};
 | |
| 		if (typeof getStyles != 'undefined') {
 | |
| 			getStyles(options, stylesReceived);
 | |
| 		} else {
 | |
| 			chrome.runtime.sendMessage(options, stylesReceived);
 | |
| 		}
 | |
| 	});
 | |
| 
 | |
| 	function stylesReceived(styles) {
 | |
| 		let numStyles = styles.length;
 | |
| 		if (numStyles === undefined) {
 | |
| 			// for 'styles' asHash:true fake the length by counting numeric ids manually
 | |
| 			numStyles = 0;
 | |
| 			for (let id of Object.keys(styles)) {
 | |
| 				numStyles += id.match(/^\d+$/) ? 1 : 0;
 | |
| 			}
 | |
| 		}
 | |
| 		const disableAll = 'disableAll' in styles ? styles.disableAll : prefs.get('disableAll');
 | |
| 		const postfix = disableAll ? 'x' : numStyles == 0 ? 'w' : '';
 | |
| 		chrome.browserAction.setIcon({
 | |
| 			path: {
 | |
| 				// Material Design 2016 new size is 16px
 | |
| 				16: '16' + postfix + '.png', 32: '32' + postfix + '.png',
 | |
| 				// Chromium forks or non-chromium browsers may still use the traditional 19px
 | |
| 				19: '19' + postfix + '.png', 38: '38' + postfix + '.png',
 | |
| 			},
 | |
| 			tabId: tab.id
 | |
| 		}, () => {
 | |
| 			// if the tab was just closed an error may occur,
 | |
| 			// e.g. 'windowPosition' pref updated in edit.js::window.onbeforeunload
 | |
| 			if (!chrome.runtime.lastError) {
 | |
| 				const text = prefs.get('show-badge') && numStyles ? String(numStyles) : '';
 | |
| 				chrome.browserAction.setBadgeText({text, tabId: tab.id});
 | |
| 				chrome.browserAction.setBadgeBackgroundColor({
 | |
| 					color: prefs.get(disableAll ? 'badgeDisabled' : 'badgeNormal')
 | |
| 				});
 | |
| 			}
 | |
| 		});
 | |
| 	}
 | |
| }
 | |
| 
 | |
| function getActiveTab(callback) {
 | |
| 	chrome.tabs.query({currentWindow: true, active: true}, function(tabs) {
 | |
| 		callback(tabs[0]);
 | |
| 	});
 | |
| }
 | |
| 
 | |
| function getActiveTabRealURL(callback) {
 | |
| 	getActiveTab(function(tab) {
 | |
| 		getTabRealURL(tab, callback);
 | |
| 	});
 | |
| }
 | |
| 
 | |
| function getTabRealURL(tab, callback) {
 | |
| 	if (tab.url != "chrome://newtab/") {
 | |
| 		callback(tab.url);
 | |
| 	} else {
 | |
| 		chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, function(frame) {
 | |
| 			frame && callback(frame.url);
 | |
| 		});
 | |
| 	}
 | |
| }
 | |
| 
 | |
| function stringAsRegExp(s, flags) {
 | |
| 	return new RegExp(s.replace(/[{}()\[\]\/\\.+?^$:=*!|]/g, "\\$&"), flags);
 | |
| }
 | |
| 
 | |
| // expands * as .*?
 | |
| function wildcardAsRegExp(s, flags) {
 | |
| 	return new RegExp(s.replace(/[{}()\[\]\/\\.+?^$:=!|]/g, "\\$&").replace(/\*/g, '.*?'), flags);
 | |
| }
 | |
| 
 | |
| var configureCommands = {
 | |
| 	get url () {
 | |
| 		return navigator.userAgent.indexOf('OPR') > -1 ?
 | |
| 			'opera://settings/configureCommands' :
 | |
| 			'chrome://extensions/configureCommands'
 | |
| 	},
 | |
| 	open: () => {
 | |
| 		chrome.tabs.create({
 | |
| 			'url': configureCommands.url
 | |
| 		});
 | |
| 	}
 | |
| };
 |