stylus/messaging.js
tophf f8d13d8dec Remove code:false mode; show sloppy regexps in popup
* Now that our own pages retrieve the styles directly via getStylesSafe the only 0.001% of cases where code:false would be needed (the browser is starting up with some of the tabs showing our built-in pages like editor or manage) is not worth optimizing for.

* According to CSS4 @document specification the entire URL must match. Stylish-for-Chrome implemented it incorrectly since the very beginning. We 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.
2017-04-18 12:46:28 +03:00

204 lines
6.3 KiB
JavaScript

/* global getStyleWithNoCode, applyOnMessage, onBackgroundMessage, getStyles */
'use strict';
// keep message channel open for sendResponse in chrome.runtime.onMessage listener
const KEEP_CHANNEL_OPEN = true;
const OWN_ORIGIN = chrome.runtime.getURL('');
const RX_SUPPORTED_URLS = new RegExp(`^(file|https?|ftps?):|^${OWN_ORIGIN}`);
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 (const 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);
}
// notify self: pref changed by background page
if (request.method == 'prefChanged' && typeof onBackgroundMessage !== 'undefined') {
onBackgroundMessage(request);
}
}
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 (const 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)
const isNTP = tab.url == 'chrome://newtab/';
if (isNTP && 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;
}
(isNTP ? getTabRealURL(tab) : Promise.resolve(tab.url))
.then(url => getStylesSafe({
matchUrl: url,
enabled: true,
asHash: true,
}))
.then(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 (const 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' : '';
const color = prefs.get(disableAll ? 'badgeDisabled' : 'badgeNormal');
const text = prefs.get('show-badge') && numStyles ? String(numStyles) : '';
chrome.browserAction.setIcon({
tabId: tab.id,
path: {
// Material Design 2016 new size is 16px
16: `images/icon/16${postfix}.png`,
32: `images/icon/32${postfix}.png`,
// Chromium forks or non-chromium browsers may still use the traditional 19px
19: `images/icon/19${postfix}.png`,
38: `images/icon/38${postfix}.png`,
// TODO: add Edge preferred sizes: 20, 25, 30, 40
},
}, ignoreChromeError);
// Vivaldi bug workaround: setBadgeText must follow setBadgeBackgroundColor
chrome.browserAction.setBadgeBackgroundColor({color});
chrome.browserAction.setBadgeText({text, tabId: tab.id});
}
}
function getActiveTab() {
return new Promise(resolve =>
chrome.tabs.query({currentWindow: true, active: true}, tabs =>
resolve(tabs[0])));
}
function getActiveTabRealURL() {
return getActiveTab()
.then(getTabRealURL);
}
function getTabRealURL(tab) {
return new Promise(resolve => {
if (tab.url != 'chrome://newtab/') {
resolve(tab.url);
} else {
chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, frame => {
resolve(frame && frame.url || '');
});
}
});
}
// opens a tab or activates the already opened one,
// reuses the New Tab page if it's focused now
function openURL({url, currentWindow = true}) {
if (!url.includes('://')) {
url = chrome.runtime.getURL(url);
}
return new Promise(resolve => {
// [some] chromium forks don't handle their fake branded protocols
url = url.replace(/^(opera|vivaldi)/, 'chrome');
// API doesn't handle the hash-fragment part
chrome.tabs.query({url: url.replace(/#.*/, ''), currentWindow}, tabs => {
for (const tab of tabs) {
if (tab.url == url) {
activateTab(tab).then(resolve);
return;
}
}
getActiveTab().then(tab => (
tab && tab.url == 'chrome://newtab/'
? chrome.tabs.update({url}, resolve)
: chrome.tabs.create({url}, resolve)
));
});
});
}
function activateTab(tab) {
return Promise.all([
new Promise(resolve => {
chrome.tabs.update(tab.id, {active: true}, resolve);
}),
new Promise(resolve => {
chrome.windows.update(tab.windowId, {focused: true}, resolve);
}),
]);
}
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);
}
function ignoreChromeError() {
chrome.runtime.lastError; // eslint-disable-line no-unused-expressions
}
const configureCommands = {
url: navigator.userAgent.includes('OPR')
? 'opera://settings/configureCommands'
: 'chrome://extensions/configureCommands',
open: () => openURL({url: configureCommands.url}),
};