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} = {}) {
if (direct) {
function openInstallPage(tab, {url = tab.url, direct, downloaded} = {}) {
if (direct && !downloaded) {
prefetchCodeForInstallation(tab.id, url);
}
return wrapReject(openURL({

View File

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

View File

@ -193,6 +193,17 @@ h2.installed.active {
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 {
text-align: center;
}

View File

@ -283,24 +283,28 @@
};
// set updateUrl
const setUpdate = $('.set-update-url input[type=checkbox]');
const updateUrl = new URL(params.get('updateUrl'));
const checker = $('.set-update-url input[type=checkbox]');
// 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');
if (dup && dup.updateUrl === updateUrl.href) {
setUpdate.checked = true;
checker.checked = true;
// 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:') {
setUpdate.checked = true;
checker.checked = true;
style.updateUrl = updateUrl.href;
}
setUpdate.onchange = e => {
if (e.target.checked) {
style.updateUrl = updateUrl.href;
} else {
delete style.updateUrl;
}
checker.onchange = () => {
style.updateUrl = checker.checked ? updateUrl.href : null;
};
checker.onchange();
$('.set-update-url p').textContent = updateUrl.href.length < 300 ? updateUrl.href :
updateUrl.href.slice(0, 300) + '...';
if (!port) {
return;

View File

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