recognize usercss @updateURL

* installation URL is preferred - same behavior as before

* @updateURL is used when the style was drag'n'dropped into the manage page
  because there's no real URL in this case

* install-usercss page shows the new update URL, which is set as per the above,
  under the checkbox that enables updates
This commit is contained in:
tophf 2017-12-06 22:35:19 +03:00
parent 0659ff6233
commit f337e18515
5 changed files with 92 additions and 70 deletions

View File

@ -78,8 +78,8 @@ var usercssHelper = (() => {
); );
} }
function openInstallPage(tab, {url = tab.url, direct} = {}) { function openInstallPage(tab, {url = tab.url, direct, downloaded} = {}) {
if (direct) { if (direct && !downloaded) {
prefetchCodeForInstallation(tab.id, url); prefetchCodeForInstallation(tab.id, url);
} }
return wrapReject(openURL({ return wrapReject(openURL({

View File

@ -64,6 +64,7 @@
<input type="checkbox"> <input type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span></span> <span></span>
<p></p>
</label> </label>
<label class="live-reload"> <label class="live-reload">
<input type="checkbox"> <input type="checkbox">

View File

@ -193,6 +193,17 @@ h2.installed.active {
min-width: 0; min-width: 0;
} }
.set-update-url {
flex-wrap: wrap;
}
.set-update-url p {
word-break: break-all;
opacity: .5;
width: 100%;
margin: .25em 0 .25em;
}
.external { .external {
text-align: center; text-align: center;
} }

View File

@ -283,24 +283,28 @@
}; };
// set updateUrl // set updateUrl
const setUpdate = $('.set-update-url input[type=checkbox]'); const checker = $('.set-update-url input[type=checkbox]');
const updateUrl = new URL(params.get('updateUrl')); // prefer the installation URL unless drag'n'dropped on the manage page
const installationUrl = (params.get('updateUrl') || '').replace(/^blob.+/, '');
const updateUrl = new URL(installationUrl || style.updateUrl || 'foo:bar');
$('.set-update-url > span').textContent = t('installUpdateFromLabel'); $('.set-update-url > span').textContent = t('installUpdateFromLabel');
if (dup && dup.updateUrl === updateUrl.href) { if (dup && dup.updateUrl === updateUrl.href) {
setUpdate.checked = true; checker.checked = true;
// there is no way to "unset" updateUrl, you can only overwrite it. // there is no way to "unset" updateUrl, you can only overwrite it.
setUpdate.disabled = true; checker.disabled = true;
} else if (updateUrl.protocol === 'foo:') {
// drag'n'dropped on the manage page and the style doesn't have @updateURL
checker.disabled = true;
} else if (updateUrl.protocol !== 'file:') { } else if (updateUrl.protocol !== 'file:') {
setUpdate.checked = true; checker.checked = true;
style.updateUrl = updateUrl.href; style.updateUrl = updateUrl.href;
} }
setUpdate.onchange = e => { checker.onchange = () => {
if (e.target.checked) { style.updateUrl = checker.checked ? updateUrl.href : null;
style.updateUrl = updateUrl.href;
} else {
delete style.updateUrl;
}
}; };
checker.onchange();
$('.set-update-url p').textContent = updateUrl.href.length < 300 ? updateUrl.href :
updateUrl.href.slice(0, 300) + '...';
if (!port) { if (!port) {
return; return;

View File

@ -3,25 +3,34 @@
// eslint-disable-next-line no-var // eslint-disable-next-line no-var
var usercss = (() => { var usercss = (() => {
// true for global, false for private // true = global
const METAS = { // false or 0 = private
__proto__: null, // <string> = global key name
author: true, // <function> = (style, newValue)
advanced: false, const KNOWN_META = new Map([
description: true, ['author', true],
homepageURL: false, ['advanced', 0],
// icon: false, ['description', true],
license: false, ['homepageURL', 'url'],
name: true, ['icon', 0],
namespace: false, ['license', 0],
// noframes: false, ['name', true],
preprocessor: false, ['namespace', 0],
supportURL: false, //['noframes', 0],
'var': false, ['preprocessor', 0],
version: false ['supportURL', 0],
}; ['updateURL', (style, newValue) => {
// always preserve locally installed style's updateUrl
if (!/^file:/.test(style.updateUrl)) {
style.updateUrl = newValue;
}
}],
['var', 0],
['version', 0],
]);
const MANDATORY_META = ['name', 'namespace', 'version'];
const META_VARS = ['text', 'color', 'checkbox', 'select', 'dropdown', 'image']; const META_VARS = ['text', 'color', 'checkbox', 'select', 'dropdown', 'image'];
const META_URLS = [...KNOWN_META.keys()].filter(k => k.endsWith('URL'));
const BUILDER = { const BUILDER = {
default: { default: {
@ -221,7 +230,7 @@ var usercss = (() => {
} }
} }
state.usercssData.vars[result.name] = result; state.usercssData.vars[result.name] = result;
validVar(result); validateVar(result);
} }
function createOption(label, value) { function createOption(label, value) {
@ -407,28 +416,39 @@ var usercss = (() => {
function doParse() { function doParse() {
let match; let match;
while ((match = re.exec(text))) { while ((match = re.exec(text))) {
state.key = match[1]; const key = state.key = match[1];
if (!(state.key in METAS)) { const route = KNOWN_META.get(key);
if (route === undefined) {
continue; continue;
} }
if (state.key === 'var' || state.key === 'advanced') { if (key === 'var' || key === 'advanced') {
if (state.key === 'advanced') { if (key === 'advanced') {
state.maybeUSO = true; state.maybeUSO = true;
} }
parseVar(state); parseVar(state);
} else { } else {
parseStringToEnd(state); parseStringToEnd(state);
usercssData[state.key] = state.value; usercssData[key] = state.value;
} }
if (state.key === 'version') { let value = state.value;
usercssData[state.key] = normalizeVersion(usercssData[state.key]); if (key === 'version') {
validVersion(usercssData[state.key]); value = usercssData[key] = normalizeVersion(value);
validateVersion(value);
} }
if (METAS[state.key]) { if (META_URLS.includes(key)) {
style[state.key] = usercssData[state.key]; validateUrl(key, value);
} }
if (state.key === 'homepageURL' || state.key === 'supportURL') { switch (typeof route) {
validUrl(usercssData[state.key]); case 'function':
route(style, value);
break;
case 'string':
style[route] = value;
break;
default:
if (route) {
style[key] = value;
}
} }
} }
} }
@ -446,12 +466,8 @@ var usercss = (() => {
if (state.maybeUSO && !usercssData.preprocessor) { if (state.maybeUSO && !usercssData.preprocessor) {
usercssData.preprocessor = 'uso'; usercssData.preprocessor = 'uso';
} }
if (usercssData.homepageURL) {
style.url = usercssData.homepageURL;
}
validate(style);
validateStyle(style);
return style; return style;
} }
@ -505,42 +521,32 @@ var usercss = (() => {
return va[prop]; return va[prop];
} }
function validate(style) { function validateStyle({usercssData: data}) {
const {usercssData: data} = style; for (const prop of MANDATORY_META) {
// mandatory fields
for (const prop of ['name', 'namespace', 'version']) {
if (!data[prop]) { if (!data[prop]) {
throw new Error(chrome.i18n.getMessage('styleMissingMeta', prop)); throw new Error(chrome.i18n.getMessage('styleMissingMeta', prop));
} }
} }
// validate version validateVersion(data.version);
validVersion(data.version); META_URLS.forEach(k => validateUrl(k, data[k]));
Object.keys(data.vars).forEach(k => validateVar(data.vars[k]));
// validate URLs
validUrl(data.homepageURL);
validUrl(data.supportURL);
// validate vars
for (const key of Object.keys(data.vars)) {
validVar(data.vars[key]);
}
} }
function validVersion(version) { function validateVersion(version) {
semverCompare(version, '0.0.0'); semverCompare(version, '0.0.0');
} }
function validUrl(url) { function validateUrl(key, url) {
if (!url) { if (!url) {
return; return;
} }
url = new URL(url); url = new URL(url);
if (url.protocol !== 'http:' && url.protocol !== 'https:') { if (!/^https?:/.test(url.protocol)) {
throw new Error(`${url.protocol} is not a valid protocol`); throw new Error(`${url.protocol} is not a valid protocol in ${key}`);
} }
} }
function validVar(va, value = 'default') { function validateVar(va, value = 'default') {
if (va.type === 'select' || va.type === 'dropdown') { if (va.type === 'select' || va.type === 'dropdown') {
if (va.options.every(o => o.name !== va[value])) { if (va.options.every(o => o.name !== va[value])) {
throw new Error(chrome.i18n.getMessage('styleMetaErrorSelectValueMismatch')); throw new Error(chrome.i18n.getMessage('styleMetaErrorSelectValueMismatch'));
@ -560,7 +566,7 @@ var usercss = (() => {
if (oldVars[key] && oldVars[key].value) { if (oldVars[key] && oldVars[key].value) {
vars[key].value = oldVars[key].value; vars[key].value = oldVars[key].value;
try { try {
validVar(vars[key], 'value'); validateVar(vars[key], 'value');
} catch (e) { } catch (e) {
vars[key].value = null; vars[key].value = null;
} }