diff --git a/background/background.js b/background/background.js index 9a55da33..e79ed593 100644 --- a/background/background.js +++ b/background/background.js @@ -28,6 +28,16 @@ chrome.runtime.onMessage.addListener(onRuntimeMessage); chrome.webNavigation.onReferenceFragmentUpdated.addListener(data => listener('styleReplaceAll', data)); + + if (FIREFOX) { + // FF applies page CSP even to content scripts, https://bugzil.la/1267027 + chrome.webNavigation.onCommitted.addListener(webNavUsercssInstallerFF, { + url: [ + {urlPrefix: 'https://raw.githubusercontent.com/', urlSuffix: '.user.css'}, + {urlPrefix: 'https://raw.githubusercontent.com/', urlSuffix: '.user.styl'}, + ] + }); + } } if (chrome.contextMenus) { @@ -242,6 +252,21 @@ function webNavigationListenerChrome(method, data) { } +function webNavUsercssInstallerFF(data) { + const {tabId} = data; + Promise.all([ + sendMessage({tabId, method: 'ping'}), + // we need tab index to open the installer next to the original one + // and also to skip the double-invocation in FF which assigns tab url later + getTab(tabId), + ]).then(([pong, tab]) => { + if (pong !== true && tab.url !== 'about:blank') { + usercssHelper.openInstallPage(tab, {direct: true}); + } + }); +} + + function updateIcon(tab, styles) { if (tab.id < 0) { return; diff --git a/background/storage.js b/background/storage.js index c34f4bee..5b1b5163 100644 --- a/background/storage.js +++ b/background/storage.js @@ -203,12 +203,18 @@ function dbExecChromeStorage(method, data) { case 'getAll': return chromeLocal.get(null).then(storage => { const styles = []; + const leftovers = []; for (const key in storage) { if (key.startsWith(STYLE_KEY_PREFIX) && Number(key.substr(STYLE_KEY_PREFIX.length))) { styles.push(storage[key]); + } else if (key.startsWith('tempUsercssCode')) { + leftovers.push(key); } } + if (leftovers.length) { + chromeLocal.remove(leftovers); + } return {target: {result: styles}}; }); } diff --git a/background/usercss-helper.js b/background/usercss-helper.js index 7edbeb96..8f46ac7e 100644 --- a/background/usercss-helper.js +++ b/background/usercss-helper.js @@ -1,4 +1,4 @@ -/* global usercss saveStyle getStyles */ +/* global usercss saveStyle getStyles chromeLocal */ 'use strict'; // eslint-disable-next-line no-var @@ -78,16 +78,29 @@ var usercssHelper = (() => { ); } - function openInstallPage(tab, request) { - const url = '/install-usercss.html' + - '?updateUrl=' + encodeURIComponent(request.updateUrl) + - '&tabId=' + tab.id; + function openInstallPage(tab, {url = tab.url, direct} = {}) { + if (direct) { + prefetchCodeForInstallation(tab.id, url); + } return wrapReject(openURL({ - url, + url: '/install-usercss.html' + + '?updateUrl=' + encodeURIComponent(url) + + '&tabId=' + (direct ? -tab.id : tab.id), index: tab.index + 1, openerTabId: tab.id, })); } + function prefetchCodeForInstallation(tabId, url) { + const key = 'tempUsercssCode' + tabId; + Promise.all([ + download(url), + chromeLocal.setValue(key, {loading: true}), + ]).then(([code]) => { + chromeLocal.setValue(key, code); + setTimeout(() => chromeLocal.remove(key), 60e3); + }); + } + return {build, save, findDup, openInstallPage}; })(); diff --git a/content/install-hook-usercss.js b/content/install-hook-usercss.js index 60156aff..aa3a4d46 100644 --- a/content/install-hook-usercss.js +++ b/content/install-hook-usercss.js @@ -65,10 +65,6 @@ function initUsercssInstall() { let watcher; chrome.runtime.onConnect.addListener(port => { - // FIXME: is this the correct way to reject a connection? - // https://developer.chrome.com/extensions/messaging#connect - console.assert(port.name === 'usercss-install'); - port.onMessage.addListener(msg => { switch (msg.method) { case 'getSourceCode': @@ -102,7 +98,7 @@ function initUsercssInstall() { }); chrome.runtime.sendMessage({ method: 'openUsercssInstallPage', - updateUrl: location.href, + url: location.href, }, r => r && r.__ERROR__ && alert(r.__ERROR__)); } diff --git a/install-usercss/install-usercss.js b/install-usercss/install-usercss.js index 0359327c..cc6c4fb4 100644 --- a/install-usercss/install-usercss.js +++ b/install-usercss/install-usercss.js @@ -1,5 +1,5 @@ /* global CodeMirror semverCompare makeLink closeCurrentTab */ -/* global messageBox */ +/* global messageBox download chromeLocal */ 'use strict'; (() => { @@ -7,30 +7,35 @@ let liveReload = false; let installed = false; - const port = chrome.tabs.connect( - Number(params.get('tabId')), - {name: 'usercss-install', frameId: 0} - ); - port.postMessage({method: 'getSourceCode'}); - port.onMessage.addListener(msg => { - switch (msg.method) { - case 'getSourceCodeResponse': - if (msg.error) { - messageBox.alert(msg.error); - } else { - initSourceCode(msg.sourceCode); - } - break; - case 'sourceCodeChanged': - if (msg.error) { - messageBox.alert(msg.error); - } else { - liveReloadUpdate(msg.sourceCode); - } - break; - } - }); - port.onDisconnect.addListener(closeCurrentTab); + const tabId = Number(params.get('tabId')); + let port; + + if (tabId < 0) { + $('.live-reload').remove(); + getCodeDirectly(); + } else { + port = chrome.tabs.connect(tabId); + port.postMessage({method: 'getSourceCode'}); + port.onMessage.addListener(msg => { + switch (msg.method) { + case 'getSourceCodeResponse': + if (msg.error) { + messageBox.alert(msg.error); + } else { + initSourceCode(msg.sourceCode); + } + break; + case 'sourceCodeChanged': + if (msg.error) { + messageBox.alert(msg.error); + } else { + liveReloadUpdate(msg.sourceCode); + } + break; + } + }); + port.onDisconnect.addListener(closeCurrentTab); + } const cm = CodeMirror($('.main'), {readOnly: true}); let liveReloadPending = Promise.resolve(); @@ -183,7 +188,7 @@ sendMessage({method: 'openEditor', id: style.id}); if (!liveReload) { - port.postMessage({method: 'closeTab'}); + chrome.runtime.sendMessage({method: 'closeTab'}); } window.dispatchEvent(new CustomEvent('installed')); @@ -252,6 +257,10 @@ } }; + if (!port) { + return; + } + // live reload const setLiveReload = $('.live-reload input[type=checkbox]'); if (updateUrl.protocol !== 'file:') { @@ -299,4 +308,38 @@ cm.setSize(null, $('.main').offsetHeight - $('.warnings').offsetHeight); } } + + function getCodeDirectly() { + // FF applies page CSP even to content scripts, https://bugzil.la/1267027 + // To circumvent that, the bg process downloads the code directly + const key = 'tempUsercssCode' + (-tabId); + chrome.storage.local.get(key, data => { + const code = data && data[key]; + + // bg already downloaded the code + if (typeof code === 'string') { + initSourceCode(code); + chrome.storage.local.remove(key); + return; + } + + // bg still downloads the code + if (code && code.loading) { + const waitForCodeInStorage = (changes, area) => { + if (area === 'local' && key in changes) { + initSourceCode(changes[key].newValue); + chrome.storage.onChanged.removeListener(waitForCodeInStorage); + chrome.storage.local.remove(key); + } + }; + chrome.storage.onChanged.addListener(waitForCodeInStorage); + return; + } + + // on the off-chance dbExecChromeStorage.getAll ran right after bg download was saved + download(params.get('updateUrl')) + .then(initSourceCode) + .catch(err => messageBox.alert(t('styleInstallFailed', String(err)))); + }); + } })();