From a339b50e27153c56657b80f4a16aced2a737341b Mon Sep 17 00:00:00 2001 From: tophf Date: Thu, 4 Jan 2018 13:36:27 +0300 Subject: [PATCH] allow live-reload without reinstalling --- _locales/en/messages.json | 4 + content/install-hook-usercss.js | 187 +++++++++++++--------------- install-usercss.html | 68 +++++----- install-usercss/install-usercss.css | 8 +- install-usercss/install-usercss.js | 45 ++++--- 5 files changed, 150 insertions(+), 162 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 1273bf0d..4b801626 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -588,6 +588,10 @@ "message": "Live reload", "description": "The label of live-reload feature" }, + "liveReloadInstallHint": { + "message": "Live reload is enabled so the installed style will be auto-updated on external changes while both this tab and the source file tab are open.", + "description": "The label of live-reload feature" + }, "liveReloadError": { "message": "An error occurred while watching the file", "description": "The label of live-reload error" diff --git a/content/install-hook-usercss.js b/content/install-hook-usercss.js index e4dcf16f..cd2b1c1c 100644 --- a/content/install-hook-usercss.js +++ b/content/install-hook-usercss.js @@ -1,7 +1,64 @@ 'use strict'; -function createSourceLoader() { - let source; +(() => { + // some weird bug in new Chrome: the content script gets injected multiple times + if (typeof window.initUsercssInstall === 'function') return; + if (!/text\/(css|plain)/.test(document.contentType) || + !/==userstyle==/i.test(document.body.textContent)) { + return; + } + window.initUsercssInstall = () => {}; + + orphanCheck(); + + const DELAY = 500; + const url = location.href; + let sourceCode, port, timer; + + chrome.runtime.onConnect.addListener(onConnected); + chrome.runtime.sendMessage({method: 'installUsercss', url}, r => + r && r.__ERROR__ && alert(r.__ERROR__)); + + function onConnected(newPort) { + port = newPort; + port.onDisconnect.addListener(stop); + port.onMessage.addListener(onMessage); + } + + function onMessage(msg, port) { + switch (msg.method) { + case 'getSourceCode': + fetchText(url) + .then(text => { + sourceCode = sourceCode || text; + port.postMessage({ + method: msg.method + 'Response', + sourceCode, + }); + }) + .catch(err => port.postMessage({ + method: msg.method + 'Response', + error: err.message || String(err), + })); + break; + + case 'liveReloadStart': + start(); + break; + + case 'liveReloadStop': + stop(); + break; + + case 'closeTab': + if (history.length > 1) { + history.back(); + } else { + chrome.runtime.sendMessage({method: 'closeTab'}); + } + break; + } + } function fetchText(url) { return new Promise((resolve, reject) => { @@ -14,104 +71,40 @@ function createSourceLoader() { }); } - function load() { - return fetchText(location.href).then(newSource => { - source = newSource; - return source; - }); + function start() { + timer = timer || setTimeout(check, DELAY); } - function watch(cb) { - let timer; - const DELAY = 1000; - - function start() { - if (timer) { - return; - } - timer = setTimeout(check, DELAY); - } - - function stop() { - clearTimeout(timer); - timer = null; - } - - function check() { - fetchText(location.href) - .then(newSource => { - if (source !== newSource) { - source = newSource; - return cb(source); - } - }) - .catch(error => { - console.log(chrome.i18n.getMessage('liveReloadError', error)); - }) - .then(() => { - timer = setTimeout(check, DELAY); - }); - } - - return {start, stop}; + function stop() { + clearTimeout(timer); + timer = null; } - return {load, watch, source: () => source}; -} - -function initUsercssInstall() { - const sourceLoader = createSourceLoader(); - const pendingSource = sourceLoader.load(); - let watcher; - - chrome.runtime.onConnect.addListener(port => { - port.onMessage.addListener(msg => { - switch (msg.method) { - case 'getSourceCode': - pendingSource - .then(sourceCode => port.postMessage({method: msg.method + 'Response', sourceCode})) - .catch(err => port.postMessage({method: msg.method + 'Response', error: err.message || String(err)})); - break; - - case 'liveReloadStart': - if (!watcher) { - watcher = sourceLoader.watch(sourceCode => { - port.postMessage({method: 'sourceCodeChanged', sourceCode}); - }); - } - watcher.start(); - break; - - case 'liveReloadStop': - watcher.stop(); - break; - - case 'closeTab': - if (history.length > 1) { - history.back(); - } else { - chrome.runtime.sendMessage({method: 'closeTab'}); - } - break; - } - }); - }); - chrome.runtime.sendMessage({ - method: 'installUsercss', - url: location.href, - }, r => r && r.__ERROR__ && alert(r.__ERROR__)); -} - -function isUsercss() { - if (!/text\/(css|plain)/.test(document.contentType)) { - return false; + function check() { + fetchText(url) + .then(text => { + if (sourceCode === text) return; + sourceCode = text; + port.postMessage({method: 'sourceCodeChanged', sourceCode}); + }) + .catch(error => { + console.log(chrome.i18n.getMessage('liveReloadError', error)); + }) + .then(() => { + timer = null; + start(); + }); } - if (!/==userstyle==/i.test(document.body.textContent)) { - return false; - } - return true; -} -if (isUsercss()) { - initUsercssInstall(); -} + function orphanCheck() { + const eventName = chrome.runtime.id + '-install-hook-usercss'; + const orphanCheckRequest = () => { + if (chrome.i18n && chrome.i18n.getUILanguage()) return true; + // In Chrome content script is orphaned on an extension update/reload + // so we need to detach event listeners + removeEventListener(eventName, orphanCheckRequest, true); + }; + dispatchEvent(new Event(eventName)); + addEventListener(eventName, orphanCheckRequest, true); + } +})(); diff --git a/install-usercss.html b/install-usercss.html index ea900b20..809ba326 100644 --- a/install-usercss.html +++ b/install-usercss.html @@ -5,50 +5,37 @@ Loading... - - - - - - - - + + + + + + + + + + - - + + - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - + + - - - + + + + + + +
@@ -60,6 +47,7 @@

+
- + diff --git a/install-usercss/install-usercss.css b/install-usercss/install-usercss.css index 19815831..ce799b10 100644 --- a/install-usercss/install-usercss.css +++ b/install-usercss/install-usercss.css @@ -156,12 +156,18 @@ h1 small { background-position: center center; } -.install:hover { +.install:hover:not(:disabled) { filter: brightness(1.1); color: #eee; text-shadow: none; } +.install:disabled { + opacity: .25; + color: white; + cursor: auto; +} + .install.reinstall:after { background-color: #333; filter: grayscale(100%); diff --git a/install-usercss/install-usercss.js b/install-usercss/install-usercss.js index 3794e434..9014cb85 100644 --- a/install-usercss/install-usercss.js +++ b/install-usercss/install-usercss.js @@ -6,7 +6,8 @@ // TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425) const params = new URLSearchParams(location.search.replace(/^\?/, '')); let liveReload = false; - let installed = false; + let installed = null; + let installedDup = null; const tabId = Number(params.get('tabId')); let tabUrl; @@ -65,7 +66,7 @@ cm.scrollTo(scrollInfo.left, scrollInfo.top); return sendMessage({ - id: installed.id, + id: (installed || installedDup).id, method: 'saveUsercss', reason: 'update', sourceCode @@ -74,19 +75,27 @@ }); } - function updateMeta(style, dup) { + function updateMeta(style, dup = installedDup) { + installedDup = dup; const data = style.usercssData; const dupData = dup && dup.usercssData; const versionTest = dup && semverCompare(data.version, dupData.version); - // update editor cm.setPreprocessor(data.preprocessor); - // update metas - document.title = `${installButtonLabel()} ${data.name}`; + const installButtonLabel = t( + installed ? 'installButtonInstalled' : + !dup ? 'installButton' : + versionTest > 0 ? 'installButtonUpdate' : 'installButtonReinstall' + ); + document.title = `${installButtonLabel} ${data.name}`; - $('.install').textContent = installButtonLabel(); - $('.install').classList.add(installButtonClass()); + $('.install').textContent = installButtonLabel; + $('.install').classList.add( + installed ? 'installed' : + !dup ? 'install' : + versionTest > 0 ? 'update' : + 'reinstall'); $('.set-update-url').title = dup && dup.updateUrl && t('installUpdateFrom', dup.updateUrl) || ''; $('.meta-name').textContent = data.name; $('.meta-version').textContent = data.version; @@ -158,20 +167,6 @@ )) ])); } - - function installButtonClass() { - return installed ? 'installed' : - !dup ? 'install' : - versionTest > 0 ? 'update' : 'reinstall'; - } - - function installButtonLabel() { - return t( - installed ? 'installButtonInstalled' : - !dup ? 'installButton' : - versionTest > 0 ? 'installButtonUpdate' : 'installButtonReinstall' - ); - } } function showError(err) { @@ -213,7 +208,7 @@ cm.setValue(sourceCode); cm.refresh(); API.buildUsercss({sourceCode, checkDup: true}) - .then(r => init(r instanceof Object ? r : deepCopy(r))) + .then(init) .catch(err => { $('.header').classList.add('meta-init-error'); showError(err); @@ -324,9 +319,11 @@ } else { setLiveReload.addEventListener('change', () => { liveReload = setLiveReload.checked; - if (installed) { + if (installed || installedDup) { const method = 'liveReload' + (liveReload ? 'Start' : 'Stop'); port.postMessage({method}); + $('.install').disabled = liveReload; + $('#live-reload-install-hint').classList.toggle('hidden', !liveReload); } }); window.addEventListener('installed', () => {