| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- /* global CodeMirror semverCompare makeLink closeCurrentTab */
- /* global messageBox download chromeLocal */
- 'use strict';
- (() => {
- const params = new URLSearchParams(location.search);
- let liveReload = false;
- let installed = false;
- const tabId = Number(params.get('tabId'));
- let port;
- if (params.has('direct')) {
- $('.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();
- window.addEventListener('resize', adjustCodeHeight);
- setTimeout(() => {
- if (!installed) {
- const div = $element({});
- $('.header').appendChild($element({
- className: 'lds-spinner',
- appendChild: new Array(12).fill(div).map(e => e.cloneNode()),
- }));
- }
- }, 200);
- function liveReloadUpdate(sourceCode) {
- liveReloadPending = liveReloadPending.then(() => {
- const scrollInfo = cm.getScrollInfo();
- const cursor = cm.getCursor();
- cm.setValue(sourceCode);
- cm.setCursor(cursor);
- cm.scrollTo(scrollInfo.left, scrollInfo.top);
- return sendMessage({
- id: installed.id,
- method: 'saveUsercss',
- reason: 'update',
- sourceCode
- }).then(updateMeta)
- .catch(showError);
- });
- }
- function updateMeta(style, 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}`;
- $('.install').textContent = installButtonLabel();
- $('.set-update-url').title = dup && dup.updateUrl && t('installUpdateFrom', dup.updateUrl) || '';
- $('.meta-name').textContent = data.name;
- $('.meta-version').textContent = data.version;
- $('.meta-description').textContent = data.description;
- if (data.author) {
- $('.meta-author').parentNode.style.display = '';
- $('.meta-author').textContent = '';
- $('.meta-author').appendChild(makeAuthor(data.author));
- } else {
- $('.meta-author').parentNode.style.display = 'none';
- }
- $('.meta-license').parentNode.style.display = data.license ? '' : 'none';
- $('.meta-license').textContent = data.license;
- $('.applies-to').textContent = '';
- getAppliesTo(style).forEach(pattern =>
- $('.applies-to').appendChild($element({tag: 'li', textContent: pattern}))
- );
- $('.external-link').textContent = '';
- const externalLink = makeExternalLink();
- if (externalLink) {
- $('.external-link').appendChild(externalLink);
- }
- $('.header').classList.add('meta-init');
- $('.header').classList.remove('meta-init-error');
- setTimeout(() => $('.lds-spinner') && $('.lds-spinner').remove(), 1000);
- showError('');
- requestAnimationFrame(adjustCodeHeight);
- function makeAuthor(text) {
- const match = text.match(/^(.+?)(?:\s+<(.+?)>)?(?:\s+\((.+?)\))$/);
- if (!match) {
- return document.createTextNode(text);
- }
- const [, name, email, url] = match;
- const frag = document.createDocumentFragment();
- if (email) {
- frag.appendChild(makeLink(`mailto:${email}`, name));
- } else {
- frag.appendChild($element({
- tag: 'span',
- textContent: name
- }));
- }
- if (url) {
- frag.appendChild(makeLink(
- url,
- $element({
- tag: 'svg#svg',
- viewBox: '0 0 20 20',
- class: 'icon',
- appendChild: $element({
- tag: 'svg#path',
- d: 'M4,4h5v2H6v8h8v-3h2v5H4V4z M11,3h6v6l-2-2l-4,4L9,9l4-4L11,3z'
- })
- })
- ));
- }
- return frag;
- }
- function makeExternalLink() {
- const urls = [];
- if (data.homepageURL) {
- urls.push([data.homepageURL, t('externalHomepage')]);
- }
- if (data.supportURL) {
- urls.push([data.supportURL, t('externalSupport')]);
- }
- if (urls.length) {
- return $element({appendChild: [
- $element({tag: 'h3', textContent: t('externalLink')}),
- $element({tag: 'ul', appendChild: urls.map(args =>
- $element({tag: 'li', appendChild: makeLink(...args)})
- )})
- ]});
- }
- }
- function installButtonLabel() {
- return t(
- installed ? 'installButtonInstalled' :
- !dup ? 'installButton' :
- versionTest > 0 ? 'installButtonUpdate' : 'installButtonReinstall'
- );
- }
- }
- function showError(err) {
- $('.warnings').textContent = '';
- if (err) {
- $('.warnings').appendChild(buildWarning(err));
- }
- $('.warnings').classList.toggle('visible', Boolean(err));
- $('.container').classList.toggle('has-warnings', Boolean(err));
- adjustCodeHeight();
- }
- function install(style) {
- installed = style;
- $$('.warning')
- .forEach(el => el.remove());
- $('.install').disabled = true;
- $('.install').classList.add('installed');
- $('.set-update-url input[type=checkbox]').disabled = true;
- $('.set-update-url').title = style.updateUrl ?
- t('installUpdateFrom', style.updateUrl) : '';
- updateMeta(style);
- sendMessage({method: 'openEditor', id: style.id});
- if (!liveReload) {
- chrome.runtime.sendMessage({method: 'closeTab'});
- }
- window.dispatchEvent(new CustomEvent('installed'));
- }
- function initSourceCode(sourceCode) {
- cm.setValue(sourceCode);
- cm.refresh();
- sendMessage({method: 'buildUsercss', sourceCode, checkDup: true})
- .then(init)
- .catch(err => {
- $('.header').classList.add('meta-init-error');
- showError(err);
- });
- }
- function buildWarning(err) {
- return $element({className: 'warning', appendChild: [
- t('parseUsercssError'),
- $element({tag: 'pre', textContent: String(err)})
- ]});
- }
- function init({style, dup}) {
- const data = style.usercssData;
- const dupData = dup && dup.usercssData;
- const versionTest = dup && semverCompare(data.version, dupData.version);
- updateMeta(style, dup);
- // update UI
- if (versionTest < 0) {
- $('.actions').parentNode.insertBefore(
- $element({className: 'warning', textContent: t('versionInvalidOlder')}),
- $('.actions')
- );
- }
- $('button.install').onclick = () => {
- (!dup ?
- Promise.resolve(true) :
- messageBox.confirm(t('styleInstallOverwrite', [
- data.name,
- dupData.version,
- data.version,
- ]))
- ).then(ok => ok &&
- sendMessage(Object.assign(style, {method: 'saveUsercss', reason: 'update'}))
- .then(install)
- .catch(err => messageBox.alert(t('styleInstallFailed', err))));
- };
- // set updateUrl
- const setUpdate = $('.set-update-url input[type=checkbox]');
- const updateUrl = new URL(params.get('updateUrl'));
- $('.set-update-url > span').textContent = t('installUpdateFromLabel');
- if (dup && dup.updateUrl === updateUrl.href) {
- setUpdate.checked = true;
- // there is no way to "unset" updateUrl, you can only overwrite it.
- setUpdate.disabled = true;
- } else if (updateUrl.protocol !== 'file:') {
- setUpdate.checked = true;
- style.updateUrl = updateUrl.href;
- }
- setUpdate.onchange = e => {
- if (e.target.checked) {
- style.updateUrl = updateUrl.href;
- } else {
- delete style.updateUrl;
- }
- };
- if (!port) {
- return;
- }
- // live reload
- const setLiveReload = $('.live-reload input[type=checkbox]');
- if (updateUrl.protocol !== 'file:') {
- setLiveReload.parentNode.remove();
- } else {
- setLiveReload.addEventListener('change', () => {
- liveReload = setLiveReload.checked;
- if (installed) {
- const method = 'liveReload' + (liveReload ? 'Start' : 'Stop');
- port.postMessage({method});
- }
- });
- window.addEventListener('installed', () => {
- if (liveReload) {
- port.postMessage({method: 'liveReloadStart'});
- }
- });
- }
- }
- function getAppliesTo(style) {
- function *_gen() {
- for (const section of style.sections) {
- for (const type of ['urls', 'urlPrefixes', 'domains', 'regexps']) {
- if (section[type]) {
- yield *section[type];
- }
- }
- }
- }
- const result = [..._gen()];
- if (!result.length) {
- result.push(chrome.i18n.getMessage('appliesToEverything'));
- }
- return result;
- }
- function adjustCodeHeight() {
- // Chrome-only bug (apparently): it doesn't limit the scroller element height
- const scroller = cm.display.scroller;
- const prevWindowHeight = adjustCodeHeight.prevWindowHeight;
- if (scroller.scrollHeight === scroller.clientHeight ||
- prevWindowHeight && window.innerHeight !== prevWindowHeight) {
- adjustCodeHeight.prevWindowHeight = window.innerHeight;
- 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))));
- });
- }
- })();
|