diff --git a/background/storage.js b/background/storage.js index af65afd4..de4f8a0c 100644 --- a/background/storage.js +++ b/background/storage.js @@ -370,7 +370,7 @@ function filterStylesInternal({ // Parse the source and find the duplication -// {id: int, style: object, source: string, checkDup: boolean} +// {id: int, style: object, sourceCode: string, checkDup: boolean} function filterUsercss(req) { let style; let pendingBuild; @@ -381,8 +381,8 @@ function filterUsercss(req) { function buildMeta() { return new Promise(resolve => { - if (req.source) { - style = usercss.buildMeta(req.source); + if (req.sourceCode) { + style = usercss.buildMeta(req.sourceCode); } else { style = req.style; } @@ -426,8 +426,8 @@ function saveUsercss(style) { function buildMeta() { return new Promise(resolve => { - if (!style.name || !style.namespace) { - resolve(Object.assign(usercss.buildMeta(style.source), style)); + if (!style.usercssData) { + resolve(Object.assign(usercss.buildMeta(style.sourceCode), style)); return; } resolve(style); @@ -449,26 +449,32 @@ function saveStyle(style) { let existed; let codeIsUpdated; - if (style.usercss) { - return processUsercss(style).then(decide); - } + const maybeProcess = style.usercssData ? processUsercss() : Promise.resolve(); - if (reason === 'update' || reason === 'update-digest') { - return calcStyleDigest(style).then(digest => { - style.originalDigest = digest; - return decide(); - }); - } - if (reason === 'import') { - style.originalDigest = style.originalDigest || style.styleDigest; // TODO: remove in the future - delete style.styleDigest; // TODO: remove in the future - if (typeof style.originalDigest !== 'string' || style.originalDigest.length !== 40) { - delete style.originalDigest; + return maybeProcess + .then(maybeUpdate) + .then(maybeImportFix) + .then(decide); + + function maybeUpdate() { + if (reason === 'update' || reason === 'update-digest') { + return calcStyleDigest(style).then(digest => { + style.originalDigest = digest; + }); } } - return decide(); - function processUsercss(style) { + function maybeImportFix() { + if (reason === 'import') { + style.originalDigest = style.originalDigest || style.styleDigest; // TODO: remove in the future + delete style.styleDigest; // TODO: remove in the future + if (typeof style.originalDigest !== 'string' || style.originalDigest.length !== 40) { + delete style.originalDigest; + } + } + } + + function processUsercss() { return findDupUsercss(style) .then(dup => { if (!dup) { @@ -477,11 +483,10 @@ function saveStyle(style) { if (!id) { id = dup.id; } - if (reason === 'config') { - return; + if (reason !== 'config') { + // preserve style.vars during update + usercss.assignVars(style, dup); } - // preserve style.vars during update - usercss.assignVars(style, dup); }) .then(() => usercss.buildCode(style)); } @@ -538,7 +543,7 @@ function saveStyle(style) { style, codeIsUpdated, reason, }); } - if (style.usercss && !existed && reason === 'install') { + if (style.usercssData && !existed && reason === 'update') { // open the editor for usercss with the first install? openEditor(style.id); } @@ -563,7 +568,13 @@ function findDupUsercss(style) { return getStyles({id: style.id}).then(s => s[0]); } return getStyles().then(styles => - styles.find(s => s.name === style.name && s.namespace === style.namespace) + styles.find(target => { + if (!target.usercssData) { + return false; + } + return target.usercssData.name === style.usercssData.name && + target.usercssData.namespace === style.usercssData.namespace; + }) ); } @@ -815,7 +826,8 @@ function normalizeStyleSections({sections}) { function calcStyleDigest(style) { - const jsonString = JSON.stringify(normalizeStyleSections(style)); + const jsonString = style.usercssData ? + style.sourceCode : JSON.stringify(normalizeStyleSections(style)); const text = new TextEncoder('utf-8').encode(jsonString); return crypto.subtle.digest('SHA-1', text).then(hex); diff --git a/background/update.js b/background/update.js index 32ef9b1a..db1c5fd3 100644 --- a/background/update.js +++ b/background/update.js @@ -64,7 +64,7 @@ var updater = { 'ignoreDigest' option is set on the second manual individual update check on the manage page. */ - const maybeUpdate = style.usercss ? maybeUpdateUsercss : maybeUpdateUSO; + const maybeUpdate = style.usercssData ? maybeUpdateUsercss : maybeUpdateUSO; return (ignoreDigest ? Promise.resolve() : calcStyleDigest(style)) .then(checkIfEdited) .then(maybeUpdate) @@ -83,9 +83,7 @@ var updater = { if (ignoreDigest) { return; } - if (style.usercss && style.edited) { - return Promise.reject(updater.EDITED); - } else if (style.originalDigest && style.originalDigest !== digest) { + if (style.originalDigest && style.originalDigest !== digest) { return Promise.reject(updater.EDITED); } } @@ -106,12 +104,14 @@ var updater = { function maybeUpdateUsercss() { return download(style.updateUrl).then(text => { const json = usercss.buildMeta(text); + const {usercssData: {version}} = style; + const {usercssData: {version: newVersion}} = json; // re-install is invalid in a soft upgrade - if (semverCompare(style.version, json.version) === 0 && !ignoreDigest) { + if (semverCompare(version, newVersion) === 0 && !ignoreDigest) { return Promise.reject(updater.SAME_VERSION); } // downgrade is always invalid - if (semverCompare(style.version, json.version) > 0) { + if (semverCompare(version, newVersion) > 0) { return Promise.reject(updater.ERROR_VERSION); } return json; @@ -121,7 +121,7 @@ var updater = { function maybeSave(json) { json.id = style.id; // no need to compare section code for usercss, they are built dynamically - if (!json.usercss) { + if (!json.usercssData) { if (!styleJSONseemsValid(json)) { return Promise.reject(updater.ERROR_JSON); } diff --git a/content/install-user-css.js b/content/install-user-css.js index b084784a..4dc073ed 100644 --- a/content/install-user-css.js +++ b/content/install-user-css.js @@ -7,7 +7,7 @@ let pendingResource; function install(style) { const request = Object.assign(style, { method: 'saveUsercss', - reason: 'install', + reason: 'update', updateUrl: location.href }); return runtimeSend(request) @@ -55,7 +55,9 @@ function getAppliesTo(style) { function initInstallPage({style, dup}, sourceLoader) { return pendingResource.then(() => { - const versionTest = dup && semverCompare(style.version, dup.version); + const data = style.usercssData; + const dupData = dup && dup.usercssData; + const versionTest = dup && semverCompare(data.version, dupData.version); document.body.textContent = ''; document.body.appendChild(buildPage()); @@ -65,13 +67,15 @@ function initInstallPage({style, dup}, sourceLoader) { $('.actions') ); } - $('.code').textContent = style.source; + $('.code').textContent = style.sourceCode; $('button.install').onclick = () => { if (dup) { - if (confirm(chrome.i18n.getMessage('styleInstallOverwrite', [style.name, dup.version, style.version]))) { + if (confirm(chrome.i18n.getMessage('styleInstallOverwrite', [ + data.name, dupData.version, data.version + ]))) { install(style); } - } else if (confirm(chrome.i18n.getMessage('styleInstall', [style.name]))) { + } else if (confirm(chrome.i18n.getMessage('styleInstall', [data.name]))) { install(style); } }; @@ -87,19 +91,19 @@ function initInstallPage({style, dup}, sourceLoader) { $element({tag: 'button', className: 'install', textContent: installButtonLabel()}) ]}), $element({tag: 'h1', appendChild: [ - style.name, - $element({tag: 'small', className: 'meta-version', textContent: style.version}) + data.name, + $element({tag: 'small', className: 'meta-version', textContent: data.version}) ]}), - $element({tag: 'p', textContent: style.description}), - style.author && $element({tag: 'h3', textContent: t('author')}), - style.author, - style.license && $element({tag: 'h3', textContent: t('license')}), - style.license, + $element({tag: 'p', textContent: data.description}), + data.author && $element({tag: 'h3', textContent: t('author')}), + data.author, + data.license && $element({tag: 'h3', textContent: t('license')}), + data.license, $element({tag: 'h3', textContent: t('appliesLabel')}), $element({tag: 'ul', appendChild: getAppliesTo(style).map( pattern => $element({tag: 'li', textContent: pattern}) )}), - externalLink(style), + externalLink(), ]}), $element({className: 'main', appendChild: [ $element({className: 'code'}) @@ -107,13 +111,13 @@ function initInstallPage({style, dup}, sourceLoader) { ]}); } - function externalLink(style) { + function externalLink() { const urls = []; - if (style.url) { - urls.push([style.url, t('externalHomepage')]); + if (data.homepageURL) { + urls.push([data.homepageURL, t('externalHomepage')]); } - if (style.supportURL) { - urls.push([style.supportURL, t('externalSupport')]); + if (data.supportURL) { + urls.push([data.supportURL, t('externalSupport')]); } if (urls.length) { return $element({appendChild: [ @@ -139,7 +143,7 @@ function initLiveReload(sourceLoader) { return runtimeSend({ method: 'saveUsercss', id: installed.id, - source: source + sourceCode: source }).then(() => { $$('.main .warning').forEach(e => e.remove()); }).catch(err => { @@ -267,7 +271,7 @@ function initUsercssInstall() { .then(() => runtimeSend({ method: 'filterUsercss', - source: sourceLoader.source(), + sourceCode: sourceLoader.source(), checkDup: true }) ) diff --git a/edit/edit.js b/edit/edit.js index 0187abdc..256ddd7e 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -1352,7 +1352,7 @@ function setStyleMeta(style) { function initWithStyle(request) { if (!editor) { - if (!request.style.usercss) { + if (!request.style.usercssData) { initWithSectionStyle(request); } else { editor = createSourceEditor(request.style); diff --git a/edit/source-editor.js b/edit/source-editor.js index 3469e98e..1a9ee4a3 100644 --- a/edit/source-editor.js +++ b/edit/source-editor.js @@ -26,7 +26,7 @@ function createSourceEditor(style) { ); // draw CodeMirror - $('#sections textarea').value = style.source; + $('#sections textarea').value = style.sourceCode; const cm = CodeMirror.fromTextArea($('#sections textarea')); // too many functions depend on this global editors.push(cm); @@ -378,8 +378,8 @@ function createSourceEditor(style) { // source cm.on('change', () => { const value = cm.getValue(); - dirty.modify('source', style.source, value); - style.source = value; + dirty.modify('source', style.sourceCode, value); + style.sourceCode = value; updateLintReportIfEnabled(cm); }); @@ -402,10 +402,11 @@ function createSourceEditor(style) { $('#name').value = style.name; $('#enabled').checked = style.enabled; $('#url').href = style.url; - cm.setOption('mode', MODE[style.preprocessor] || 'css'); - CodeMirror.autoLoadMode(cm, style.preprocessor || 'css'); + const {usercssData: {preprocessor}} = style; + cm.setOption('mode', MODE[preprocessor] || 'css'); + CodeMirror.autoLoadMode(cm, MODE[preprocessor] || 'css'); // beautify only works with regular CSS - $('#beautify').disabled = Boolean(style.preprocessor); + $('#beautify').disabled = MODE[preprocessor] && MODE[preprocessor] !== 'css'; updateTitle(); } @@ -417,9 +418,9 @@ function createSourceEditor(style) { function replaceStyle(newStyle) { style = deepCopy(newStyle); updateMetas(); - if (style.source !== cm.getValue()) { + if (style.sourceCode !== cm.getValue()) { const cursor = cm.getCursor(); - cm.setValue(style.source); + cm.setValue(style.sourceCode); cm.setCursor(cursor); } dirty.clear(); @@ -448,8 +449,7 @@ function createSourceEditor(style) { reason: 'editSave', id: style.id, enabled: style.enabled, - edited: dirty.has('source'), - source: style.source + sourceCode: style.sourceCode }; return onBackgroundReady().then(() => BG.saveUsercss(req)) .then(result => { diff --git a/js/usercss.js b/js/usercss.js index 2841efd5..e0d1745f 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -4,10 +4,23 @@ // eslint-disable-next-line no-var var usercss = (function () { - const METAS = [ - 'author', 'advanced', 'description', 'homepageURL', 'icon', 'license', 'name', - 'namespace', 'noframes', 'preprocessor', 'supportURL', 'var', 'version' - ]; + // true for global, false for private + const METAS = { + __proto__: null, + author: true, + advanced: false, + description: true, + homepageURL: false, + // icon: false, + license: false, + name: true, + namespace: false, + // noframes: false, + preprocessor: false, + supportURL: false, + 'var': false, + version: false + }; const META_VARS = ['text', 'color', 'checkbox', 'select', 'dropdown', 'image']; @@ -222,7 +235,7 @@ var usercss = (function () { parseStringToEnd(state); result.default = state.value; } - state.style.vars[result.name] = result; + state.usercssData.vars[result.name] = result; } function parseEOT(state) { @@ -319,72 +332,71 @@ var usercss = (function () { return s; } - function _buildMeta(source) { - const style = { - name: null, - usercss: true, - version: null, - source: source, - edited: false, - enabled: true, - sections: [], - vars: {}, - preprocessor: null, - noframes: false + function _buildMeta(sourceCode) { + const usercssData = { + vars: {} }; - const text = getMetaSource(source); + const style = { + enabled: true, + sourceCode, + sections: [], + usercssData + }; + + const text = getMetaSource(sourceCode); const re = /@(\w+)\s+/mg; - const state = {style, re, text}; + const state = {style, re, text, usercssData}; let match; while ((match = re.exec(text))) { state.key = match[1]; - if (!METAS.includes(state.key)) { + if (!(state.key in METAS)) { continue; } - if (state.key === 'noframes') { - style.noframes = true; - } else if (state.key === 'var' || state.key === 'advanced') { + if (state.key === 'var' || state.key === 'advanced') { if (state.key === 'advanced') { state.maybeUSO = true; } parseVar(state); } else { parseStringToEnd(state); - if (state.key === 'homepageURL') { - style.url = state.value; - } else { - style[state.key] = state.value; - } + usercssData[state.key] = state.value; + } + if (METAS[state.key]) { + style[state.key] = usercssData[state.key]; } } - if (state.maybeUSO && !style.preprocessor) { - style.preprocessor = 'uso'; + if (state.maybeUSO && !usercssData.preprocessor) { + usercssData.preprocessor = 'uso'; + } + if (usercssData.homepageURL) { + style.url = usercssData.homepageURL; } return style; } function buildCode(style) { + const {usercssData: {preprocessor, vars}, sourceCode} = style; let builder; - if (style.preprocessor) { - if (!BUILDER.hasOwnProperty(style.preprocessor)) { - return Promise.reject(new Error(`Unsupported preprocessor: ${style.preprocessor}`)); + if (preprocessor) { + if (!BUILDER[preprocessor]) { + return Promise.reject(new Error(`Unsupported preprocessor: ${preprocessor}`)); } - builder = BUILDER[style.preprocessor]; + builder = BUILDER[preprocessor]; } else { builder = BUILDER.default; } - const vars = simpleVars(style.vars); + const sVars = simpleVars(vars); return Promise.resolve().then(() => { // preprocess if (builder.preprocess) { - return builder.preprocess(style.source, vars); + return builder.preprocess(sourceCode, sVars); } - return style.source; + return sourceCode; }).then(mozStyle => // moz-parser loadScript('/js/moz-parser.js').then(() => @@ -395,7 +407,7 @@ var usercss = (function () { ).then(() => { // postprocess if (builder.postprocess) { - return builder.postprocess(style.sections, vars); + return builder.postprocess(style.sections, sVars); } }).then(() => style); } @@ -414,22 +426,23 @@ var usercss = (function () { } function validate(style) { + const {usercssData: data} = style; // mandatory fields for (const prop of ['name', 'namespace', 'version']) { - if (!style[prop]) { + if (!data[prop]) { throw new Error(chrome.i18n.getMessage('styleMissingMeta', prop)); } } // validate version - semverCompare(style.version, '0.0.0'); + semverCompare(data.version, '0.0.0'); // validate URLs - validUrl(style.url); - validUrl(style.supportURL); + validUrl(data.homepageURL); + validUrl(data.supportURL); // validate vars - for (const key of Object.keys(style.vars)) { - validVar(style.vars[key]); + for (const key of Object.keys(data.vars)) { + validVar(data.vars[key]); } } @@ -456,14 +469,16 @@ var usercss = (function () { } function assignVars(style, old) { + const {usercssData: {vars}} = style; + const {usercssData: {vars: oldVars}} = old; // The type of var might be changed during the update. Set value to null if the value is invalid. - for (const key of Object.keys(style.vars)) { - if (old.vars[key] && old.vars[key].value) { - style.vars[key].value = old.vars[key].value; + for (const key of Object.keys(vars)) { + if (oldVars[key] && oldVars[key].value) { + vars[key].value = oldVars[key].value; try { - validVar(style.vars[key], 'value'); + validVar(vars[key], 'value'); } catch (e) { - style.vars[key].value = null; + vars[key].value = null; } } } diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 25d7635e..b802ac4a 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -27,7 +27,7 @@ function configDialog(style) { function buildConfigForm() { const labels = []; - const vars = deepCopy(style.vars); + const vars = deepCopy(style.usercssData.vars); for (const key of Object.keys(vars)) { const va = vars[key]; let appendChild; diff --git a/manage/manage.js b/manage/manage.js index 2400a7a9..3200a6e9 100644 --- a/manage/manage.js +++ b/manage/manage.js @@ -193,7 +193,7 @@ function createStyleElement({style, name}) { if (style.updateUrl && newUI.enabled) { $('.actions', entry).appendChild(template.updaterIcons.cloneNode(true)); } - if (style.vars && Object.keys(style.vars).length && newUI.enabled) { + if (shouldShowConfig() && newUI.enabled) { $('.actions', entry).appendChild(template.configureIcon.cloneNode(true)); } @@ -202,6 +202,13 @@ function createStyleElement({style, name}) { createStyleTargetsElement({entry, style, postponeFavicons: name}); return entry; + + function shouldShowConfig() { + if (!style.usercssData) { + return false; + } + return Object.keys(style.usercssData.vars).length > 0; + } } @@ -293,7 +300,7 @@ Object.assign(handleEvent, { } style.reason = 'config'; for (const key of keys) { - style.vars[key].value = vars[key].value; + style.usercssData.vars[key].value = vars[key].value; } saveStyleSafe(style); });