2017-09-09 11:29:35 +00:00
|
|
|
/* global loadScript mozParser semverCompare */
|
2017-08-05 16:49:25 +00:00
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-var
|
|
|
|
var usercss = (function () {
|
2017-09-16 01:24:50 +00:00
|
|
|
// 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
|
|
|
|
};
|
2017-09-05 00:16:08 +00:00
|
|
|
|
2017-09-15 05:40:04 +00:00
|
|
|
const META_VARS = ['text', 'color', 'checkbox', 'select', 'dropdown', 'image'];
|
|
|
|
|
2017-08-05 16:49:25 +00:00
|
|
|
const BUILDER = {
|
|
|
|
default: {
|
|
|
|
postprocess(sections, vars) {
|
2017-09-12 15:19:16 +00:00
|
|
|
const varDef =
|
2017-09-12 12:24:25 +00:00
|
|
|
':root {\n' +
|
|
|
|
Object.keys(vars).map(k => ` --${k}: ${vars[k].value};\n`).join('') +
|
|
|
|
'}\n';
|
2017-08-05 16:49:25 +00:00
|
|
|
|
|
|
|
for (const section of sections) {
|
|
|
|
section.code = varDef + section.code;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
stylus: {
|
|
|
|
preprocess(source, vars) {
|
2017-09-12 15:19:16 +00:00
|
|
|
return loadScript('/vendor/stylus-lang/stylus.min.js').then(() => (
|
2017-08-05 16:49:25 +00:00
|
|
|
new Promise((resolve, reject) => {
|
|
|
|
let varDef = '';
|
|
|
|
for (const key of Object.keys(vars)) {
|
|
|
|
varDef += `${key} = ${vars[key].value};\n`;
|
|
|
|
}
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-undef
|
|
|
|
stylus(varDef + source).render((err, output) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
} else {
|
|
|
|
resolve(output);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
})
|
|
|
|
));
|
|
|
|
}
|
2017-09-15 05:40:04 +00:00
|
|
|
},
|
|
|
|
uso: {
|
|
|
|
preprocess(source, vars) {
|
|
|
|
const pool = new Map();
|
|
|
|
return Promise.resolve(doReplace(source));
|
|
|
|
|
|
|
|
function getValue(name, rgb) {
|
|
|
|
if (!vars.hasOwnProperty(name)) {
|
|
|
|
if (name.endsWith('-rgb')) {
|
|
|
|
return getValue(name.slice(0, -4), true);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (rgb) {
|
|
|
|
if (vars[name].type === 'color') {
|
|
|
|
// eslint-disable-next-line no-use-before-define
|
|
|
|
const color = colorParser.parse(vars[name].value);
|
|
|
|
return `${color.r}, ${color.g}, ${color.b}`;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (vars[name].type === 'dropdown') {
|
|
|
|
// prevent infinite recursion
|
|
|
|
pool.set('');
|
|
|
|
return doReplace(vars[name].value);
|
|
|
|
}
|
|
|
|
return vars[name].value;
|
|
|
|
}
|
|
|
|
|
|
|
|
function doReplace(text) {
|
|
|
|
return text.replace(/\/\*\[\[([\w-]+)\]\]\*\//g, (match, name) => {
|
|
|
|
if (!pool.has(name)) {
|
|
|
|
const value = getValue(name);
|
|
|
|
pool.set(name, value === null ? match : value);
|
|
|
|
}
|
|
|
|
return pool.get(name);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2017-08-05 16:49:25 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-09-05 20:26:01 +00:00
|
|
|
const colorParser = (function () {
|
|
|
|
const el = document.createElement('div');
|
|
|
|
// https://bugs.webkit.org/show_bug.cgi?id=14563
|
|
|
|
document.head.appendChild(el);
|
|
|
|
|
2017-09-12 12:25:59 +00:00
|
|
|
function parseRGB(color) {
|
2017-09-05 20:26:01 +00:00
|
|
|
const [r, g, b, a = 1] = color.match(/[.\d]+/g).map(Number);
|
|
|
|
return {r, g, b, a};
|
|
|
|
}
|
|
|
|
|
|
|
|
function parse(color) {
|
|
|
|
el.style.color = color;
|
|
|
|
if (el.style.color === '') {
|
2017-09-11 17:48:10 +00:00
|
|
|
throw new Error(chrome.i18n.getMessage('styleMetaErrorColor', color));
|
2017-09-05 20:26:01 +00:00
|
|
|
}
|
|
|
|
color = getComputedStyle(el).color;
|
|
|
|
el.style.color = '';
|
2017-09-12 12:25:59 +00:00
|
|
|
return parseRGB(color);
|
2017-09-05 20:26:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function format({r, g, b, a = 1}) {
|
2017-09-12 12:29:09 +00:00
|
|
|
if (a === 1) {
|
|
|
|
return `rgb(${r}, ${g}, ${b})`;
|
|
|
|
}
|
2017-09-05 20:26:01 +00:00
|
|
|
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
|
|
}
|
|
|
|
|
|
|
|
function formatHex({r, g, b, a = null}) {
|
2017-09-12 12:44:12 +00:00
|
|
|
let hex = '#' + (0x1000000 + (r << 16) + (g << 8) + (b | 0)).toString(16).substr(1);
|
2017-09-05 20:26:01 +00:00
|
|
|
if (a !== null) {
|
2017-09-12 12:44:12 +00:00
|
|
|
hex += (0x100 + Math.floor(a * 255)).toString(16).substr(1);
|
2017-09-05 20:26:01 +00:00
|
|
|
}
|
2017-09-12 12:44:12 +00:00
|
|
|
return hex;
|
2017-09-05 20:26:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return {parse, format, formatHex};
|
|
|
|
})();
|
|
|
|
|
2017-08-05 16:49:25 +00:00
|
|
|
function getMetaSource(source) {
|
|
|
|
const commentRe = /\/\*[\s\S]*?\*\//g;
|
|
|
|
const metaRe = /==userstyle==[\s\S]*?==\/userstyle==/i;
|
|
|
|
|
|
|
|
let m;
|
|
|
|
// iterate through each comment
|
|
|
|
while ((m = commentRe.exec(source))) {
|
|
|
|
const commentSource = source.slice(m.index, m.index + m[0].length);
|
|
|
|
const n = commentSource.match(metaRe);
|
|
|
|
if (n) {
|
|
|
|
return n[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function buildMeta(source) {
|
|
|
|
const style = _buildMeta(source);
|
|
|
|
validate(style);
|
|
|
|
return style;
|
|
|
|
}
|
|
|
|
|
2017-09-16 02:11:08 +00:00
|
|
|
function parseWord(state, error = 'invalid word') {
|
|
|
|
const match = state.text.slice(state.re.lastIndex).match(/^([\w-]+)\s*/);
|
2017-09-15 05:40:04 +00:00
|
|
|
if (!match) {
|
|
|
|
throw new Error(error);
|
2017-09-05 00:16:08 +00:00
|
|
|
}
|
2017-09-15 05:40:04 +00:00
|
|
|
state.value = match[1];
|
|
|
|
state.re.lastIndex += match[0].length;
|
2017-09-05 00:16:08 +00:00
|
|
|
}
|
|
|
|
|
2017-09-15 05:40:04 +00:00
|
|
|
function parseVar(state) {
|
2017-09-05 00:16:08 +00:00
|
|
|
const result = {
|
2017-09-15 05:40:04 +00:00
|
|
|
type: null,
|
2017-09-05 00:16:08 +00:00
|
|
|
label: null,
|
|
|
|
name: null,
|
|
|
|
value: null,
|
|
|
|
default: null,
|
2017-09-15 05:40:04 +00:00
|
|
|
options: null
|
2017-09-05 00:16:08 +00:00
|
|
|
};
|
|
|
|
|
2017-09-15 05:40:04 +00:00
|
|
|
parseWord(state, 'missing type');
|
|
|
|
result.type = state.type = state.value;
|
|
|
|
if (!META_VARS.includes(state.type)) {
|
|
|
|
throw new Error(`unknown type: ${state.type}`);
|
2017-09-05 00:16:08 +00:00
|
|
|
}
|
|
|
|
|
2017-09-15 05:40:04 +00:00
|
|
|
parseWord(state, 'missing name');
|
|
|
|
result.name = state.value;
|
|
|
|
|
|
|
|
parseString(state);
|
|
|
|
result.label = state.value;
|
|
|
|
|
|
|
|
if (state.type === 'checkbox') {
|
|
|
|
const match = state.text.slice(state.re.lastIndex).match(/([01])\s+/);
|
|
|
|
if (!match) {
|
|
|
|
throw new Error('value must be 0 or 1');
|
|
|
|
}
|
|
|
|
state.re.lastIndex += match[0].length;
|
|
|
|
result.default = match[1];
|
|
|
|
} else if (state.type === 'select' || (state.type === 'image' && state.key === 'var')) {
|
|
|
|
parseJSON(state);
|
|
|
|
result.options = Object.keys(state.value).map(k => ({
|
|
|
|
label: k,
|
|
|
|
value: state.value[k]
|
|
|
|
}));
|
|
|
|
result.default = result.options[0].value;
|
|
|
|
} else if (state.type === 'dropdown' || state.type === 'image') {
|
|
|
|
if (state.text[state.re.lastIndex] !== '{') {
|
|
|
|
throw new Error('no open {');
|
|
|
|
}
|
|
|
|
result.options = [];
|
|
|
|
state.re.lastIndex++;
|
|
|
|
while (state.text[state.re.lastIndex] !== '}') {
|
|
|
|
const option = {};
|
|
|
|
|
|
|
|
parseStringUnquoted(state);
|
|
|
|
option.name = state.value;
|
|
|
|
|
|
|
|
parseString(state);
|
|
|
|
option.label = state.value;
|
|
|
|
|
|
|
|
if (state.type === 'dropdown') {
|
|
|
|
parseEOT(state);
|
|
|
|
} else {
|
|
|
|
parseString(state);
|
|
|
|
}
|
|
|
|
option.value = state.value;
|
|
|
|
|
|
|
|
result.options.push(option);
|
|
|
|
}
|
|
|
|
state.re.lastIndex++;
|
|
|
|
eatWhitespace(state);
|
|
|
|
result.default = result.options[0].value;
|
|
|
|
} else {
|
|
|
|
// text, color
|
|
|
|
parseStringToEnd(state);
|
|
|
|
result.default = state.value;
|
2017-09-05 00:16:08 +00:00
|
|
|
}
|
2017-09-16 01:24:50 +00:00
|
|
|
state.usercssData.vars[result.name] = result;
|
2017-09-15 05:40:04 +00:00
|
|
|
}
|
2017-09-05 00:16:08 +00:00
|
|
|
|
2017-09-15 05:40:04 +00:00
|
|
|
function parseEOT(state) {
|
|
|
|
const match = state.text.slice(state.re.lastIndex).match(/^<<<EOT([\s\S]+?)EOT;/);
|
|
|
|
if (!match) {
|
|
|
|
throw new Error('missing EOT');
|
|
|
|
}
|
|
|
|
state.re.lastIndex += match[0].length;
|
|
|
|
state.value = match[1].trim().replace(/\*\\\//g, '*/');
|
|
|
|
eatWhitespace(state);
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseStringUnquoted(state) {
|
|
|
|
const match = state.text.slice(state.re.lastIndex).match(/^[^"]*/);
|
|
|
|
state.re.lastIndex += match[0].length;
|
|
|
|
state.value = match[0].trim().replace(/\s+/g, '-');
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseString(state) {
|
2017-09-16 02:11:08 +00:00
|
|
|
const match = state.text.slice(state.re.lastIndex).match(
|
|
|
|
/^((['"])(?:\\\2|[^\n])*?\2|\w+)\s*/);
|
2017-09-15 05:40:04 +00:00
|
|
|
state.re.lastIndex += match[0].length;
|
|
|
|
state.value = unquote(match[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseJSON(state) {
|
|
|
|
const result = looseJSONParse(state.text.slice(state.re.lastIndex));
|
2017-09-16 02:11:08 +00:00
|
|
|
if (result) {
|
|
|
|
state.re.lastIndex += result.length;
|
|
|
|
state.value = result.json;
|
|
|
|
} else {
|
|
|
|
// fallback to our parser
|
|
|
|
parseJSONValue(state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseJSONValue(state) {
|
|
|
|
const JSON_PRIME = {
|
|
|
|
__proto__: null,
|
|
|
|
'null': null,
|
|
|
|
'true': true,
|
|
|
|
'false': false
|
|
|
|
};
|
|
|
|
if (state.text[state.re.lastIndex] === '{') {
|
|
|
|
// object
|
|
|
|
const obj = {};
|
|
|
|
state.re.lastIndex++;
|
|
|
|
eatWhitespace(state);
|
|
|
|
while (state.text[state.re.lastIndex] !== '}') {
|
|
|
|
parseString(state);
|
|
|
|
const key = state.value;
|
|
|
|
if (state.text[state.re.lastIndex] !== ':') {
|
|
|
|
throw new Error('missing \':\'');
|
|
|
|
}
|
|
|
|
state.re.lastIndex++;
|
|
|
|
eatWhitespace(state);
|
|
|
|
parseJSONValue(state);
|
|
|
|
obj[key] = state.value;
|
|
|
|
if (state.text[state.re.lastIndex] === ',') {
|
|
|
|
state.re.lastIndex++;
|
|
|
|
eatWhitespace(state);
|
|
|
|
} else if (state.text[state.re.lastIndex] !== '}') {
|
|
|
|
throw new Error('missing \',\' or \'}\'');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
state.re.lastIndex++;
|
|
|
|
eatWhitespace(state);
|
|
|
|
state.value = obj;
|
|
|
|
} else if (state.text[state.re.lastIndex] === '[') {
|
|
|
|
// array
|
|
|
|
const arr = [];
|
|
|
|
state.re.lastIndex++;
|
|
|
|
eatWhitespace(state);
|
|
|
|
while (state.text[state.re.lastIndex] !== ']') {
|
|
|
|
parseJSONValue(state);
|
|
|
|
arr.push(state.value);
|
|
|
|
if (state.text[state.re.lastIndex] === ',') {
|
|
|
|
state.re.lastIndex++;
|
|
|
|
eatWhitespace(state);
|
|
|
|
} else if (state.text[state.re.lastIndex] !== ']') {
|
|
|
|
throw new Error('missing \',\' or \']\'');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
state.re.lastIndex++;
|
|
|
|
eatWhitespace(state);
|
|
|
|
state.value = arr;
|
|
|
|
} else if (state.text[state.re.lastIndex] === '"') {
|
|
|
|
// string
|
|
|
|
parseString(state);
|
|
|
|
} else if (/\d/.test(state.text[state.re.lastIndex])) {
|
|
|
|
// number
|
|
|
|
parseNumber(state);
|
|
|
|
} else {
|
|
|
|
parseWord(state);
|
|
|
|
if (!(state.value in JSON_PRIME)) {
|
|
|
|
throw new Error(`unknown literal '${state.value}'`);
|
|
|
|
}
|
|
|
|
state.value = JSON_PRIME[state.value];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseNumber(state) {
|
|
|
|
const match = state.slice(state.re.lastIndex).match(/^-?\d+(\.\d+)?\s*/);
|
|
|
|
if (!match) {
|
|
|
|
throw new Error('invalid number');
|
|
|
|
}
|
|
|
|
state.value = Number(match[0].trim());
|
|
|
|
state.re.lastIndex += match[0].length;
|
2017-09-15 05:40:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function looseJSONParse(text) {
|
|
|
|
try {
|
|
|
|
return {
|
|
|
|
json: JSON.parse(text),
|
|
|
|
length: text.length
|
|
|
|
};
|
|
|
|
} catch (e) {
|
|
|
|
let pos;
|
|
|
|
{
|
|
|
|
const match = e.message.match(/after json data at line (\d+) column (\d+)/i);
|
|
|
|
if (match) {
|
|
|
|
const [, line, column] = match.map(Number);
|
|
|
|
pos = indexFromLine(text, line - 1) + column - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pos === undefined) {
|
|
|
|
const match = e.message.match(/at position (\d+)/);
|
|
|
|
if (match) {
|
|
|
|
pos = Number(match[1]);
|
|
|
|
}
|
|
|
|
}
|
2017-09-16 02:11:08 +00:00
|
|
|
if (!pos) {
|
|
|
|
return null;
|
2017-09-11 17:48:10 +00:00
|
|
|
}
|
2017-09-16 02:11:08 +00:00
|
|
|
try {
|
|
|
|
return {
|
|
|
|
json: JSON.parse(text.slice(0, pos)),
|
|
|
|
length: pos
|
|
|
|
};
|
|
|
|
} catch (e2) {}
|
2017-09-15 05:40:04 +00:00
|
|
|
throw e;
|
2017-09-05 00:16:08 +00:00
|
|
|
}
|
2017-09-15 05:40:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function indexFromLine(text, line) {
|
|
|
|
if (line === 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
const re = /\r?\n/g;
|
|
|
|
let i = 1;
|
|
|
|
while (re.exec(text)) {
|
|
|
|
if (i++ === line) {
|
|
|
|
return re.lastIndex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw new Error('out of range');
|
|
|
|
}
|
2017-09-05 00:16:08 +00:00
|
|
|
|
2017-09-15 05:40:04 +00:00
|
|
|
function eatWhitespace(state) {
|
|
|
|
const match = state.text.slice(state.re.lastIndex).match(/\s*/);
|
|
|
|
state.re.lastIndex += match[0].length;
|
|
|
|
}
|
2017-09-05 00:16:08 +00:00
|
|
|
|
2017-09-15 05:40:04 +00:00
|
|
|
function parseStringToEnd(state) {
|
|
|
|
const match = state.text.slice(state.re.lastIndex).match(/.+/);
|
|
|
|
state.value = unquote(match[0].trim());
|
|
|
|
state.re.lastIndex += match[0].length;
|
|
|
|
}
|
|
|
|
|
|
|
|
function unquote(s) {
|
|
|
|
const match = s.match(/^(['"])(.*)\1$/);
|
|
|
|
if (match) {
|
|
|
|
return match[2];
|
|
|
|
}
|
|
|
|
return s;
|
2017-09-05 00:16:08 +00:00
|
|
|
}
|
|
|
|
|
2017-09-16 01:24:50 +00:00
|
|
|
function _buildMeta(sourceCode) {
|
2017-09-18 00:16:11 +00:00
|
|
|
sourceCode = sourceCode.replace(/\r\n?/g, '\n');
|
|
|
|
|
2017-09-16 01:24:50 +00:00
|
|
|
const usercssData = {
|
|
|
|
vars: {}
|
|
|
|
};
|
|
|
|
|
2017-08-05 16:49:25 +00:00
|
|
|
const style = {
|
|
|
|
enabled: true,
|
2017-09-16 01:24:50 +00:00
|
|
|
sourceCode,
|
2017-08-05 16:49:25 +00:00
|
|
|
sections: [],
|
2017-09-16 01:24:50 +00:00
|
|
|
usercssData
|
2017-08-05 16:49:25 +00:00
|
|
|
};
|
|
|
|
|
2017-09-16 01:24:50 +00:00
|
|
|
const text = getMetaSource(sourceCode);
|
2017-09-15 05:40:04 +00:00
|
|
|
const re = /@(\w+)\s+/mg;
|
2017-09-16 01:24:50 +00:00
|
|
|
const state = {style, re, text, usercssData};
|
2017-08-05 16:49:25 +00:00
|
|
|
|
2017-09-15 05:40:04 +00:00
|
|
|
let match;
|
|
|
|
while ((match = re.exec(text))) {
|
|
|
|
state.key = match[1];
|
2017-09-16 01:24:50 +00:00
|
|
|
if (!(state.key in METAS)) {
|
2017-09-05 00:16:08 +00:00
|
|
|
continue;
|
|
|
|
}
|
2017-09-16 01:24:50 +00:00
|
|
|
if (state.key === 'var' || state.key === 'advanced') {
|
2017-09-15 05:40:04 +00:00
|
|
|
if (state.key === 'advanced') {
|
|
|
|
state.maybeUSO = true;
|
|
|
|
}
|
|
|
|
parseVar(state);
|
2017-08-05 16:49:25 +00:00
|
|
|
} else {
|
2017-09-15 05:40:04 +00:00
|
|
|
parseStringToEnd(state);
|
2017-09-16 01:24:50 +00:00
|
|
|
usercssData[state.key] = state.value;
|
2017-08-05 16:49:25 +00:00
|
|
|
}
|
2017-09-16 01:24:50 +00:00
|
|
|
if (METAS[state.key]) {
|
|
|
|
style[state.key] = usercssData[state.key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (state.maybeUSO && !usercssData.preprocessor) {
|
|
|
|
usercssData.preprocessor = 'uso';
|
2017-09-05 00:16:08 +00:00
|
|
|
}
|
2017-09-16 01:24:50 +00:00
|
|
|
if (usercssData.homepageURL) {
|
|
|
|
style.url = usercssData.homepageURL;
|
2017-09-15 05:40:04 +00:00
|
|
|
}
|
2017-08-05 16:49:25 +00:00
|
|
|
|
|
|
|
return style;
|
|
|
|
}
|
|
|
|
|
|
|
|
function buildCode(style) {
|
2017-09-16 01:24:50 +00:00
|
|
|
const {usercssData: {preprocessor, vars}, sourceCode} = style;
|
2017-08-05 16:49:25 +00:00
|
|
|
let builder;
|
2017-09-16 01:24:50 +00:00
|
|
|
if (preprocessor) {
|
|
|
|
if (!BUILDER[preprocessor]) {
|
|
|
|
return Promise.reject(new Error(`Unsupported preprocessor: ${preprocessor}`));
|
2017-09-11 16:08:54 +00:00
|
|
|
}
|
2017-09-16 01:24:50 +00:00
|
|
|
builder = BUILDER[preprocessor];
|
2017-08-05 16:49:25 +00:00
|
|
|
} else {
|
|
|
|
builder = BUILDER.default;
|
|
|
|
}
|
|
|
|
|
2017-09-16 01:24:50 +00:00
|
|
|
const sVars = simpleVars(vars);
|
2017-09-01 06:38:46 +00:00
|
|
|
|
2017-08-05 16:49:25 +00:00
|
|
|
return Promise.resolve().then(() => {
|
|
|
|
// preprocess
|
|
|
|
if (builder.preprocess) {
|
2017-09-16 01:24:50 +00:00
|
|
|
return builder.preprocess(sourceCode, sVars);
|
2017-08-05 16:49:25 +00:00
|
|
|
}
|
2017-09-16 01:24:50 +00:00
|
|
|
return sourceCode;
|
2017-08-05 16:49:25 +00:00
|
|
|
}).then(mozStyle =>
|
|
|
|
// moz-parser
|
|
|
|
loadScript('/js/moz-parser.js').then(() =>
|
|
|
|
mozParser.parse(mozStyle).then(sections => {
|
|
|
|
style.sections = sections;
|
|
|
|
})
|
|
|
|
)
|
|
|
|
).then(() => {
|
|
|
|
// postprocess
|
|
|
|
if (builder.postprocess) {
|
2017-09-16 01:24:50 +00:00
|
|
|
return builder.postprocess(style.sections, sVars);
|
2017-08-05 16:49:25 +00:00
|
|
|
}
|
|
|
|
}).then(() => style);
|
|
|
|
}
|
|
|
|
|
2017-09-01 06:38:46 +00:00
|
|
|
function simpleVars(vars) {
|
|
|
|
// simplify vars by merging `va.default` to `va.value`, so BUILDER don't
|
|
|
|
// need to test each va's default value.
|
|
|
|
return Object.keys(vars).reduce((output, key) => {
|
|
|
|
const va = vars[key];
|
2017-09-15 05:40:04 +00:00
|
|
|
output[key] = Object.assign({}, va, {
|
2017-09-01 06:38:46 +00:00
|
|
|
value: va.value === null || va.value === undefined ?
|
2017-09-15 05:40:04 +00:00
|
|
|
va.default : va.value,
|
|
|
|
});
|
2017-09-01 06:38:46 +00:00
|
|
|
return output;
|
|
|
|
}, {});
|
|
|
|
}
|
|
|
|
|
2017-08-05 16:49:25 +00:00
|
|
|
function validate(style) {
|
2017-09-16 01:24:50 +00:00
|
|
|
const {usercssData: data} = style;
|
2017-08-05 16:49:25 +00:00
|
|
|
// mandatory fields
|
|
|
|
for (const prop of ['name', 'namespace', 'version']) {
|
2017-09-16 01:24:50 +00:00
|
|
|
if (!data[prop]) {
|
2017-08-05 16:49:25 +00:00
|
|
|
throw new Error(chrome.i18n.getMessage('styleMissingMeta', prop));
|
|
|
|
}
|
|
|
|
}
|
2017-09-09 11:29:35 +00:00
|
|
|
// validate version
|
2017-09-16 01:24:50 +00:00
|
|
|
semverCompare(data.version, '0.0.0');
|
2017-09-09 11:29:35 +00:00
|
|
|
|
2017-09-11 18:46:37 +00:00
|
|
|
// validate URLs
|
2017-09-16 01:24:50 +00:00
|
|
|
validUrl(data.homepageURL);
|
|
|
|
validUrl(data.supportURL);
|
2017-09-11 18:46:37 +00:00
|
|
|
|
2017-09-09 11:29:35 +00:00
|
|
|
// validate vars
|
2017-09-16 01:24:50 +00:00
|
|
|
for (const key of Object.keys(data.vars)) {
|
|
|
|
validVar(data.vars[key]);
|
2017-09-09 11:29:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-11 18:46:37 +00:00
|
|
|
function validUrl(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`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-09 11:29:35 +00:00
|
|
|
function validVar(va, value = 'default') {
|
2017-09-15 05:40:04 +00:00
|
|
|
if (va.type === 'select' || va.type === 'dropdown') {
|
|
|
|
if (va.options.every(o => o.value !== va[value])) {
|
|
|
|
throw new Error(chrome.i18n.getMessage('invalid select value'));
|
|
|
|
}
|
2017-09-09 11:29:35 +00:00
|
|
|
} else if (va.type === 'checkbox' && !/^[01]$/.test(va[value])) {
|
2017-09-11 17:48:10 +00:00
|
|
|
throw new Error(chrome.i18n.getMessage('styleMetaErrorCheckbox'));
|
2017-09-09 11:29:35 +00:00
|
|
|
} else if (va.type === 'color') {
|
|
|
|
va[value] = colorParser.format(colorParser.parse(va[value]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function assignVars(style, old) {
|
2017-09-16 01:24:50 +00:00
|
|
|
const {usercssData: {vars}} = style;
|
|
|
|
const {usercssData: {vars: oldVars}} = old;
|
2017-09-09 11:29:35 +00:00
|
|
|
// The type of var might be changed during the update. Set value to null if the value is invalid.
|
2017-09-16 01:24:50 +00:00
|
|
|
for (const key of Object.keys(vars)) {
|
|
|
|
if (oldVars[key] && oldVars[key].value) {
|
|
|
|
vars[key].value = oldVars[key].value;
|
2017-09-09 11:29:35 +00:00
|
|
|
try {
|
2017-09-16 01:24:50 +00:00
|
|
|
validVar(vars[key], 'value');
|
2017-09-12 12:48:03 +00:00
|
|
|
} catch (e) {
|
2017-09-16 01:24:50 +00:00
|
|
|
vars[key].value = null;
|
2017-09-09 11:29:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-08-05 16:49:25 +00:00
|
|
|
}
|
|
|
|
|
2017-09-09 11:29:35 +00:00
|
|
|
return {buildMeta, buildCode, colorParser, assignVars};
|
2017-08-05 16:49:25 +00:00
|
|
|
})();
|