Merge branch 'master' into dev-root

This commit is contained in:
eight 2019-03-04 15:06:08 +08:00
commit f05c13722b
13 changed files with 432 additions and 105 deletions

View File

@ -315,6 +315,15 @@
"message": "Enable", "message": "Enable",
"description": "Label for the button to enable a style" "description": "Label for the button to enable a style"
}, },
"excludeStyleByDomainLabel": {
"message": "Exclude the current domain"
},
"excludeStyleByUrlLabel": {
"message": "Exclude the current URL"
},
"excludeStyleByUrlRedundant": {
"message": "The current URL is the domain page"
},
"exportLabel": { "exportLabel": {
"message": "Export", "message": "Export",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
@ -1026,6 +1035,10 @@
"message": "Stylus failed to parse usercss:", "message": "Stylus failed to parse usercss:",
"description": "The error message to show when stylus failed to parse usercss" "description": "The error message to show when stylus failed to parse usercss"
}, },
"popupAutoResort": {
"message": "Resort styles in popup after toggling",
"description": "Label for the checkbox controlling popup resorting."
},
"popupBorders": { "popupBorders": {
"message": "Add white borders on the sides" "message": "Add white borders on the sides"
}, },
@ -1044,6 +1057,10 @@
"message": "Shift-click or right-click opens manager with styles applicable for current site", "message": "Shift-click or right-click opens manager with styles applicable for current site",
"description": "Tooltip for the 'Manage' button in the popup." "description": "Tooltip for the 'Manage' button in the popup."
}, },
"popupMenuButtonTooltip": {
"message": "Action menu",
"description": "Tooltip for menu button in popup."
},
"popupOpenEditInWindow": { "popupOpenEditInWindow": {
"message": "Open editor in a new window", "message": "Open editor in a new window",
"description": "Label for the checkbox controlling 'edit' action behavior in the popup." "description": "Label for the checkbox controlling 'edit' action behavior in the popup."
@ -1056,10 +1073,6 @@
"message": "Styles before commands", "message": "Styles before commands",
"description": "Label for the checkbox controlling section order in the popup." "description": "Label for the checkbox controlling section order in the popup."
}, },
"popupAutoResort": {
"message": "Resort styles in popup after toggling",
"description": "Label for the checkbox controlling popup resorting."
},
"prefShowBadge": { "prefShowBadge": {
"message": "Number of styles active for the current site", "message": "Number of styles active for the current site",
"description": "Label for the checkbox controlling toolbar badge text." "description": "Label for the checkbox controlling toolbar badge text."

View File

@ -22,6 +22,11 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, {
styleExists: styleManager.styleExists, styleExists: styleManager.styleExists,
toggleStyle: styleManager.toggleStyle, toggleStyle: styleManager.toggleStyle,
addInclusion: styleManager.addInclusion,
removeInclusion: styleManager.removeInclusion,
addExclusion: styleManager.addExclusion,
removeExclusion: styleManager.removeExclusion,
getTabUrlPrefix() { getTabUrlPrefix() {
return this.sender.tab.url.match(/^([\w-]+:\/+[^/#]+)/)[1]; return this.sender.tab.url.match(/^([\w-]+:\/+[^/#]+)/)[1];
}, },
@ -42,9 +47,11 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, {
// Chrome 49 doesn't report own extension pages in webNavigation apparently // Chrome 49 doesn't report own extension pages in webNavigation apparently
// so we do a force update which doesn't use the cache. // so we do a force update which doesn't use the cache.
if (CHROME && CHROME < 2661 && this.sender.tab.url.startsWith(URLS.ownOrigin)) { if (CHROME && CHROME < 2661 && this.sender.tab.url.startsWith(URLS.ownOrigin)) {
return updateIconBadgeForce(this.sender.tab.id, count); updateIconBadgeForce(this.sender.tab.id, count);
} else {
updateIconBadge(this.sender.tab.id, count);
} }
return updateIconBadge(this.sender.tab.id, count); return true;
}, },
// exposed for stuff that requires followup sendMessage() like popup::openSettings // exposed for stuff that requires followup sendMessage() like popup::openSettings
@ -79,8 +86,8 @@ if (FIREFOX) {
// FF applies page CSP even to content scripts, https://bugzil.la/1267027 // FF applies page CSP even to content scripts, https://bugzil.la/1267027
navigatorUtil.onCommitted(webNavUsercssInstallerFF, { navigatorUtil.onCommitted(webNavUsercssInstallerFF, {
url: [ url: [
{hostSuffix: '.githubusercontent.com', urlSuffix: '.user.css'}, {pathSuffix: '.user.css'},
{hostSuffix: '.githubusercontent.com', urlSuffix: '.user.styl'}, {pathSuffix: '.user.styl'},
] ]
}); });
// FF misses some about:blank iframes so we inject our content script explicitly // FF misses some about:blank iframes so we inject our content script explicitly

View File

@ -57,10 +57,13 @@ const styleManager = (() => {
importStyle, importStyle,
importMany, importMany,
toggleStyle, toggleStyle,
setStyleExclusions,
getAllStyles, // used by import-export getAllStyles, // used by import-export
getStylesByUrl, // used by popup getStylesByUrl, // used by popup
styleExists, styleExists,
addExclusion,
removeExclusion,
addInclusion,
removeInclusion
}); });
function handleLivePreviewConnections() { function handleLivePreviewConnections() {
@ -92,6 +95,11 @@ const styleManager = (() => {
}); });
} }
function escapeRegExp(text) {
// https://github.com/lodash/lodash/blob/0843bd46ef805dd03c0c8d804630804f3ba0ca3c/lodash.js#L152
return text.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
}
function get(id, noCode = false) { function get(id, noCode = false) {
const data = styles.get(id).data; const data = styles.get(id).data;
return noCode ? getStyleWithNoCode(data) : data; return noCode ? getStyleWithNoCode(data) : data;
@ -177,14 +185,51 @@ const styleManager = (() => {
} else { } else {
data = Object.assign(createNewStyle(), data); data = Object.assign(createNewStyle(), data);
} }
data.updateDate = Date.now();
return saveStyle(data) return saveStyle(data)
.then(newData => handleSave(newData, 'editSave')); .then(newData => handleSave(newData, 'editSave'));
} }
function setStyleExclusions(id, exclusions) { function addIncludeExclude(id, rule, type) {
const data = Object.assign({}, styles.get(id).data, {exclusions}); const data = Object.assign({}, styles.get(id).data);
if (!data[type]) {
data[type] = [];
}
if (data[type].includes(rule)) {
throw new Error('The rule already exists');
}
data[type] = data[type].concat([rule]);
return saveStyle(data) return saveStyle(data)
.then(newData => handleSave(newData, 'exclusions')); .then(newData => handleSave(newData, 'styleSettings'));
}
function removeIncludeExclude(id, rule, type) {
const data = Object.assign({}, styles.get(id).data);
if (!data[type]) {
return;
}
if (!data[type].includes(rule)) {
return;
}
data[type] = data[type].filter(r => r !== rule);
return saveStyle(data)
.then(newData => handleSave(newData, 'styleSettings'));
}
function addExclusion(id, rule) {
return addIncludeExclude(id, rule, 'exclusions');
}
function removeExclusion(id, rule) {
return removeIncludeExclude(id, rule, 'exclusions');
}
function addInclusion(id, rule) {
return addIncludeExclude(id, rule, 'inclusions');
}
function removeInclusion(id, rule) {
return removeIncludeExclude(id, rule, 'inclusions');
} }
function deleteStyle(id) { function deleteStyle(id) {
@ -478,14 +523,7 @@ const styleManager = (() => {
} }
function buildGlob(text) { function buildGlob(text) {
const prefix = text[0] === '^' ? '' : '\\b'; return '^' + escapeRegExp(text).replace(/\\\\\\\*|\\\*/g, m => m.length > 2 ? m : '.*') + '$';
const suffix = text[text.length - 1] === '$' ? '' : '\\b';
return `${prefix}${escape(text)}${suffix}`;
function escape(text) {
// FIXME: using .* everywhere is slow
return text.replace(/[.*]/g, m => m === '.' ? '\\.' : '.*');
}
} }
function getDomain(url) { function getDomain(url) {

View File

@ -304,7 +304,7 @@ const APPLY = (() => {
method: 'invokeAPI', method: 'invokeAPI',
name: 'updateIconBadge', name: 'updateIconBadge',
args: [styleInjector.list.length] args: [styleInjector.list.length]
}).catch(msg.ignoreError); }).catch(console.error);
} }
function rootReady() { function rootReady() {

View File

@ -157,10 +157,18 @@
'text-align-all': true, 'text-align-all': true,
'contain': true, 'contain': true,
'mask-image': true,
'mix-blend-mode': true, 'mix-blend-mode': true,
'rotate': true,
'isolation': true, 'isolation': true,
'zoom': true, 'zoom': true,
// https://www.w3.org/TR/css-round-display-1/
'border-boundary': true,
'shape': true,
'shape-inside': true,
'viewport-fit': true,
// nonstandard https://compat.spec.whatwg.org/ // nonstandard https://compat.spec.whatwg.org/
'box-reflect': true, 'box-reflect': true,
'text-fill-color': true, 'text-fill-color': true,
@ -171,6 +179,7 @@
}); });
Object.assign(CodeMirror.mimeModes['text/css'].valueKeywords, { Object.assign(CodeMirror.mimeModes['text/css'].valueKeywords, {
'isolate': true, 'isolate': true,
'rect': true,
'recto': true, 'recto': true,
'verso': true, 'verso': true,
}); });

View File

@ -197,6 +197,7 @@ function createSectionsEditor({style, onTitleChanged}) {
dirty.modify('enabled', style.enabled, newValue); dirty.modify('enabled', style.enabled, newValue);
style.enabled = newValue; style.enabled = newValue;
enabledEl.checked = newValue; enabledEl.checked = newValue;
updateLivePreview();
} }
function nextEditor(cm, cycle = true) { function nextEditor(cm, cycle = true) {
@ -565,6 +566,7 @@ function createSectionsEditor({style, onTitleChanged}) {
$('#heading').textContent = t('editStyleHeading'); $('#heading').textContent = t('editStyleHeading');
} }
livePreview.show(Boolean(style.id)); livePreview.show(Boolean(style.id));
updateLivePreview();
}); });
function reinit() { function reinit() {

View File

@ -307,9 +307,9 @@
// set updateUrl // set updateUrl
const checker = $('.set-update-url input[type=checkbox]'); const checker = $('.set-update-url input[type=checkbox]');
// prefer the installation URL unless drag'n'dropped on the manage page // only use the installation URL if not specified in usercss
const installationUrl = (params.get('updateUrl') || '').replace(/^blob.+/, ''); const installationUrl = (params.get('updateUrl') || '').replace(/^blob.+/, '');
const updateUrl = new URL(installationUrl || style.updateUrl || DUMMY_URL); const updateUrl = new URL(style.updateUrl || installationUrl || DUMMY_URL);
if (dup && dup.updateUrl === updateUrl.href) { if (dup && dup.updateUrl === updateUrl.href) {
checker.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.
@ -335,7 +335,7 @@
// live reload // live reload
const setLiveReload = $('.live-reload input[type=checkbox]'); const setLiveReload = $('.live-reload input[type=checkbox]');
if (updateUrl.protocol !== 'file:') { if (!installationUrl || !installationUrl.startsWith('file:')) {
setLiveReload.parentNode.remove(); setLiveReload.parentNode.remove();
} else { } else {
setLiveReload.addEventListener('change', () => { setLiveReload.addEventListener('change', () => {

View File

@ -7,7 +7,7 @@ const usercss = (() => {
author: undefined, author: undefined,
description: undefined, description: undefined,
homepageURL: 'url', homepageURL: 'url',
// updateURL: 'updateUrl', updateURL: 'updateUrl',
name: undefined, name: undefined,
}; };
const RX_META = /\/\*!?\s*==userstyle==[\s\S]*?==\/userstyle==\s*\*\//i; const RX_META = /\/\*!?\s*==userstyle==[\s\S]*?==\/userstyle==\s*\*\//i;

View File

@ -24,25 +24,55 @@
<template data-id="style"> <template data-id="style">
<div class="entry"> <div class="entry">
<div class="main-controls"> <div class="entry-content">
<label class="style-name"> <div class="main-controls">
<input class="checker" type="checkbox"> <label class="style-name">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg> <input class="checker" type="checkbox">
</label> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="actions">
<a href="#" class="configure" i18n-title="configureStyle" tabindex="0">
<svg class="svg-icon config"><use xlink:href="#svg-icon-config"></use></svg>
</a>
<a class="style-edit-link" href="edit.html?id=" i18n-title="editStyleLabel" tabindex="0">
<svg class="svg-icon edit" viewBox="0 0 14 16">
<path fill-rule="evenodd" d="M0 12v3h3l8-8-3-3-8 8zm3 2H1v-2h1v1h1v1zm10.3-9.3L12 6 9 3l1.3-1.3a.996.996 0 0 1 1.41 0l1.59 1.59c.39.39.39 1.02 0 1.41z"/>
</svg>
</a>
<a href="#" class="menu-button" i18n-title="popupMenuButtonTooltip" tabindex="0">
<svg class="svg-icon menu-button-icon" viewBox="0 0 3 16">
<path fill-rule="evenodd" d="M0 2.5a1.5 1.5 0 1 0 3 0 1.5 1.5 0 0 0-3 0zm0 5a1.5 1.5 0 1 0 3 0 1.5 1.5 0 0 0-3 0zM1.5 14a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/>
</svg>
</a>
</div>
</div> </div>
<div class="actions"> <div class="menu">
<a href="#" class="configure" i18n-title="configureStyle" tabindex="0"> <label class="menu-item exclude-by-domain button">
<svg class="svg-icon config"><use xlink:href="#svg-icon-config"></use></svg> <div class="menu-icon">
</a> <div class="checkbox-container">
<a class="style-edit-link" href="edit.html?id=" i18n-title="editStyleLabel" tabindex="0"> <input type="checkbox" class="exclude-by-domain-checkbox">
<svg class="svg-icon edit" viewBox="0 0 14 16"> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<path fill-rule="evenodd" d="M0 12v3h3l8-8-3-3-8 8zm3 2H1v-2h1v1h1v1zm10.3-9.3L12 6 9 3l1.3-1.3a.996.996 0 0 1 1.41 0l1.59 1.59c.39.39.39 1.02 0 1.41z"/> </div>
</svg> </div>
</a> <span i18n-text="excludeStyleByDomainLabel"></span>
<a href="#" class="delete" i18n-title="deleteStyleLabel" tabindex="0"> </label>
<svg class="svg-icon remove" viewBox="0 0 14 16"> <label class="menu-item exclude-by-url button">
<path fill-rule="evenodd" d="M11 2H9c0-.55-.45-1-1-1H5c-.55 0-1 .45-1 1H2c-.55 0-1 .45-1 1v1c0 .55.45 1 1 1v9c0 .55.45 1 1 1h7c.55 0 1-.45 1-1V5c.55 0 1-.45 1-1V3c0-.55-.45-1-1-1zm-1 12H3V5h1v8h1V5h1v8h1V5h1v8h1V5h1v9zm1-10H2V3h9v1z"/> <div class="menu-icon">
</svg> <div class="checkbox-container">
<input type="checkbox" class="exclude-by-url-checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</div>
</div>
<span i18n-text="excludeStyleByUrlLabel"></span>
</label>
<a href="#" class="menu-item delete">
<div class="menu-icon">
<svg class="svg-icon remove" viewBox="0 0 14 16">
<path fill-rule="evenodd" d="M11 2H9c0-.55-.45-1-1-1H5c-.55 0-1 .45-1 1H2c-.55 0-1 .45-1 1v1c0 .55.45 1 1 1v9c0 .55.45 1 1 1h7c.55 0 1-.45 1-1V5c.55 0 1-.45 1-1V3c0-.55-.45-1-1-1zm-1 12H3V5h1v8h1V5h1v8h1V5h1v8h1V5h1v9zm1-10H2V3h9v1z"/>
</svg>
</div>
<span i18n-text="deleteStyleLabel"></span>
</a> </a>
</div> </div>
</div> </div>
@ -241,8 +271,8 @@
<path d="M6.2,0C5.8,0,5.4,0.4,5.4,0.8v0.7C5,1.7,4.6,1.8,4.3,2L3.8,1.5C3.6,1.4,3.4,1.3,3.2,1.3S2.7,1.4,2.6,1.5L1.5,2.6c-0.3,0.3-0.3,0.9,0,1.2L2,4.3C1.8,4.6,1.7,5,1.5,5.4H0.8C0.4,5.4,0,5.8,0,6.2v1.5c0,0.5,0.4,0.8,0.8,0.8h0.7C1.7,9,1.8,9.4,2,9.7l-0.5,0.5c-0.3,0.3-0.3,0.8,0,1.2l1.1,1.1c0.3,0.3,0.9,0.3,1.2,0L4.3,12c0.4,0.2,0.8,0.4,1.2,0.5v0.7c0,0.5,0.4,0.8,0.8,0.8h1.5c0.5,0,0.8-0.4,0.8-0.8v-0.7C9,12.3,9.4,12.2,9.7,12l0.5,0.5c0.3,0.3,0.9,0.3,1.2,0l1.1-1.1c0.3-0.3,0.3-0.8,0-1.2L12,9.7c0.2-0.4,0.4-0.8,0.5-1.2h0.7c0.5,0,0.8-0.4,0.8-0.8V6.2c0-0.5-0.4-0.8-0.8-0.8h-0.7C12.3,5,12.2,4.6,12,4.3l0.5-0.5c0.3-0.3,0.3-0.9,0-1.2l-1.1-1.1c-0.2-0.2-0.4-0.2-0.6-0.2s-0.4,0.1-0.6,0.2L9.7,2C9.4,1.8,9,1.7,8.6,1.5V0.8C8.6,0.4,8.2,0,7.8,0L6.2,0z M6.8,0.8h0.4c0.2,0,0.4,0.2,0.4,0.4v1.2c0.8,0.1,1.6,0.4,2.3,0.9l0.8-0.8c0.2-0.2,0.4-0.2,0.6,0l0.3,0.3c0.2,0.2,0.2,0.4,0,0.6l-0.8,0.8c0.5,0.7,0.8,1.4,0.9,2.3h1.2c0.2,0,0.4,0.2,0.4,0.4v0.4c0,0.2-0.2,0.4-0.4,0.4h-1.2c-0.1,0.8-0.4,1.6-0.9,2.3l0.8,0.8c0.2,0.2,0.2,0.4,0,0.6l-0.3,0.3c-0.2,0.2-0.4,0.2-0.6,0l-0.8-0.8c-0.7,0.5-1.4,0.8-2.3,0.9v1.2c0,0.2-0.2,0.4-0.4,0.4H6.8c-0.2,0-0.4-0.2-0.4-0.4v-1.2c-0.8-0.1-1.6-0.4-2.3-0.9l-0.8,0.8c-0.2,0.2-0.4,0.2-0.6,0l-0.3-0.3c-0.2-0.2-0.2-0.4,0-0.6l0.8-0.8C2.8,9.2,2.5,8.4,2.4,7.6H1.2C1,7.6,0.8,7.4,0.8,7.2V6.8c0-0.2,0.2-0.4,0.4-0.4h1.2c0.1-0.8,0.4-1.6,0.9-2.3L2.5,3.3c-0.2-0.2-0.2-0.4,0-0.6l0.3-0.3c0.2-0.2,0.4-0.2,0.6,0l0.8,0.8c0.7-0.5,1.4-0.8,2.3-0.9V1.2C6.4,1,6.6,0.8,6.8,0.8L6.8,0.8z M7,3.6C5.1,3.6,3.6,5.1,3.6,7c0,0,0,0,0,0c0,1.9,1.5,3.4,3.4,3.4c1.9,0,3.4-1.5,3.4-3.4C10.4,5.1,8.9,3.6,7,3.6C7,3.6,7,3.6,7,3.6z M7,4.8c1.2,0,2.2,1,2.2,2.2c0,1.2-1,2.2-2.2,2.2c-1.2,0-2.2-1-2.2-2.2C4.8,5.8,5.8,4.8,7,4.8z"/> <path d="M6.2,0C5.8,0,5.4,0.4,5.4,0.8v0.7C5,1.7,4.6,1.8,4.3,2L3.8,1.5C3.6,1.4,3.4,1.3,3.2,1.3S2.7,1.4,2.6,1.5L1.5,2.6c-0.3,0.3-0.3,0.9,0,1.2L2,4.3C1.8,4.6,1.7,5,1.5,5.4H0.8C0.4,5.4,0,5.8,0,6.2v1.5c0,0.5,0.4,0.8,0.8,0.8h0.7C1.7,9,1.8,9.4,2,9.7l-0.5,0.5c-0.3,0.3-0.3,0.8,0,1.2l1.1,1.1c0.3,0.3,0.9,0.3,1.2,0L4.3,12c0.4,0.2,0.8,0.4,1.2,0.5v0.7c0,0.5,0.4,0.8,0.8,0.8h1.5c0.5,0,0.8-0.4,0.8-0.8v-0.7C9,12.3,9.4,12.2,9.7,12l0.5,0.5c0.3,0.3,0.9,0.3,1.2,0l1.1-1.1c0.3-0.3,0.3-0.8,0-1.2L12,9.7c0.2-0.4,0.4-0.8,0.5-1.2h0.7c0.5,0,0.8-0.4,0.8-0.8V6.2c0-0.5-0.4-0.8-0.8-0.8h-0.7C12.3,5,12.2,4.6,12,4.3l0.5-0.5c0.3-0.3,0.3-0.9,0-1.2l-1.1-1.1c-0.2-0.2-0.4-0.2-0.6-0.2s-0.4,0.1-0.6,0.2L9.7,2C9.4,1.8,9,1.7,8.6,1.5V0.8C8.6,0.4,8.2,0,7.8,0L6.2,0z M6.8,0.8h0.4c0.2,0,0.4,0.2,0.4,0.4v1.2c0.8,0.1,1.6,0.4,2.3,0.9l0.8-0.8c0.2-0.2,0.4-0.2,0.6,0l0.3,0.3c0.2,0.2,0.2,0.4,0,0.6l-0.8,0.8c0.5,0.7,0.8,1.4,0.9,2.3h1.2c0.2,0,0.4,0.2,0.4,0.4v0.4c0,0.2-0.2,0.4-0.4,0.4h-1.2c-0.1,0.8-0.4,1.6-0.9,2.3l0.8,0.8c0.2,0.2,0.2,0.4,0,0.6l-0.3,0.3c-0.2,0.2-0.4,0.2-0.6,0l-0.8-0.8c-0.7,0.5-1.4,0.8-2.3,0.9v1.2c0,0.2-0.2,0.4-0.4,0.4H6.8c-0.2,0-0.4-0.2-0.4-0.4v-1.2c-0.8-0.1-1.6-0.4-2.3-0.9l-0.8,0.8c-0.2,0.2-0.4,0.2-0.6,0l-0.3-0.3c-0.2-0.2-0.2-0.4,0-0.6l0.8-0.8C2.8,9.2,2.5,8.4,2.4,7.6H1.2C1,7.6,0.8,7.4,0.8,7.2V6.8c0-0.2,0.2-0.4,0.4-0.4h1.2c0.1-0.8,0.4-1.6,0.9-2.3L2.5,3.3c-0.2-0.2-0.2-0.4,0-0.6l0.3-0.3c0.2-0.2,0.4-0.2,0.6,0l0.8,0.8c0.7-0.5,1.4-0.8,2.3-0.9V1.2C6.4,1,6.6,0.8,6.8,0.8L6.8,0.8z M7,3.6C5.1,3.6,3.6,5.1,3.6,7c0,0,0,0,0,0c0,1.9,1.5,3.4,3.4,3.4c1.9,0,3.4-1.5,3.4-3.4C10.4,5.1,8.9,3.6,7,3.6C7,3.6,7,3.6,7,3.6z M7,4.8c1.2,0,2.2,1,2.2,2.2c0,1.2-1,2.2-2.2,2.2c-1.2,0-2.2-1-2.2-2.2C4.8,5.8,5.8,4.8,7,4.8z"/>
</symbol> </symbol>
<symbol id="svg-icon-config-uso" viewBox="0 0 14 14"> <symbol id="svg-icon-config-uso" viewBox="0 0 20 20">
<path d="M2,3h4v2H4v6h6V9h2v4H2V3z M8,1h6v6l-2.2-2.2l-4,4L6.2,7.2l4-4L8,1z"/> <path d="M4,4h5v2H6v8h8v-3h2v5H4V4z M11,3h6v6l-2-2l-4,4L9,9l4-4L11,3z"/>
</symbol> </symbol>
<symbol id="svg-icon-help" viewBox="0 0 14 16"> <symbol id="svg-icon-help" viewBox="0 0 14 16">

View File

@ -173,16 +173,17 @@ body.blocked > DIV {
} }
/* entry */ /* entry */
.entry { .entry {
position: relative;
}
.entry-content {
display: flex; display: flex;
align-items: center; align-items: center;
height: 26px; height: 26px;
padding: 0 14px 0 0; padding: 0 14px 0 0;
position: relative;
} }
html[style] .entry { html[style] .entry-content {
padding: 0 16px 0 0; padding: 0 16px 0 0;
} }
@ -192,6 +193,7 @@ html[style] .entry {
.entry .actions { .entry .actions {
display: inline-flex; display: inline-flex;
align-items: center;
} }
.style-name { .style-name {
@ -264,9 +266,34 @@ html[style*="border"] .entry:nth-child(11):before {
} }
.entry .actions > * { .entry .actions > * {
display: inline-block; height: 26px;
padding: 0 1px; width: 18px;
margin: 0 1px; display: inline-flex;
align-items: center;
justify-content: center;
}
.entry .actions > .menu-button {
width: 14px;
}
.entry .actions > a.configure {
padding-right: 2px;
}
.entry .actions > a.configure[target="_blank"] {
width: 20px;
}
.svg-icon.config {
height: 14px;
width: 14px;
}
a.configure[target="_blank"] .svg-icon.config {
height: 20px;
width: 20px;
margin-top: 1px;
} }
.not-applied .checker, .not-applied .checker,
@ -286,6 +313,72 @@ html[style*="border"] .entry:nth-child(11):before {
color: darkred; color: darkred;
} }
/* entry menu */
.entry .menu {
display: flex;
flex-direction: column;
top: 100%;
width: 100%;
z-index: 1;
box-sizing: border-box;
height: 0;
transition: height .25s ease-out, opacity .5s ease-in;
overflow: hidden;
opacity: 0;
}
.entry.menu-active .menu {
/* FIXME: avoid hard coded height */
height: 72px;
opacity: 1;
}
/* accessibility */
.menu-item {
display: none;
border: none;
align-items: center;
padding: 0 0 0 20px;
height: 24px;
background: none;
text-decoration: none;
}
.entry.menu-active.accessible-items .menu-item {
display: flex;
}
.entry .menu-item.delete {
cursor: pointer;
}
.entry .menu-item.delete:hover {
color: #000;
}
.entry .menu-item > span {
margin-top: 1px;
}
.entry .menu-item:hover,
.entry .menu-item:active {
background-color: rgba(0, 0, 0, 0.1);
transition: background-color .25s;
}
.entry .menu-icon {
width: 26px;
}
.entry .menu-icon > * {
display: block;
margin: 0 auto;
}
.entry .menu-item.disabled {
opacity: 0.5;
background-color: transparent;
cursor: help;
}
/* checkbox */
.checkbox-container {
position: relative;
display: inline-block;
width: 12px;
height: 12px;
}
.regexp-problem-indicator { .regexp-problem-indicator {
background-color: #d00; background-color: #d00;
width: 14px; width: 14px;

View File

@ -309,6 +309,11 @@ function createStyleElement(style) {
indicator.appendChild(document.createTextNode('!')); indicator.appendChild(document.createTextNode('!'));
indicator.onclick = handleEvent.indicator; indicator.onclick = handleEvent.indicator;
$('.main-controls', entry).appendChild(indicator); $('.main-controls', entry).appendChild(indicator);
$('.menu-button', entry).onclick = handleEvent.toggleMenu;
$('.exclude-by-domain-checkbox', entry).onchange = e => handleEvent.toggleExclude(e, 'domain');
$('.exclude-by-url-checkbox', entry).onchange = e => handleEvent.toggleExclude(e, 'url');
} }
style = Object.assign(entry.styleMeta, style); style = Object.assign(entry.styleMeta, style);
@ -329,9 +334,35 @@ function createStyleElement(style) {
entry.classList.toggle('not-applied', style.excluded || style.sloppy); entry.classList.toggle('not-applied', style.excluded || style.sloppy);
entry.classList.toggle('regexp-partial', style.sloppy); entry.classList.toggle('regexp-partial', style.sloppy);
$('.exclude-by-domain-checkbox', entry).checked = styleExcluded(style, 'domain');
const excludeByUrlCheckbox = $('.exclude-by-url-checkbox', entry);
const isRedundant = getExcludeRule('domain') === getExcludeRule('url');
excludeByUrlCheckbox.checked = !isRedundant && styleExcluded(style, 'url');
excludeByUrlCheckbox.disabled = isRedundant;
const excludeByUrlLabel = $('.exclude-by-url', entry);
excludeByUrlLabel.classList.toggle('disabled', isRedundant);
excludeByUrlLabel.title = isRedundant ?
chrome.i18n.getMessage('excludeStyleByUrlRedundant') : '';
return entry; return entry;
} }
function styleExcluded({exclusions}, type) {
if (!exclusions) {
return false;
}
const rule = getExcludeRule(type);
return exclusions.includes(rule);
}
function getExcludeRule(type) {
if (type === 'domain') {
return new URL(tabURL).origin + '/*';
}
return tabURL + '*';
}
Object.assign(handleEvent, { Object.assign(handleEvent, {
@ -356,10 +387,29 @@ Object.assign(handleEvent, {
.then(sortStylesInPlace); .then(sortStylesInPlace);
}, },
toggleExclude(event, type) {
const entry = handleEvent.getClickedStyleElement(event);
if (event.target.checked) {
API.addExclusion(entry.styleMeta.id, getExcludeRule(type));
} else {
API.removeExclusion(entry.styleMeta.id, getExcludeRule(type));
}
},
toggleMenu(event) {
const entry = handleEvent.getClickedStyleElement(event);
entry.classList.toggle('menu-active');
setTimeout(() => {
entry.classList.toggle('accessible-items');
}, 250);
event.preventDefault();
},
delete(event) { delete(event) {
const entry = handleEvent.getClickedStyleElement(event); const entry = handleEvent.getClickedStyleElement(event);
const id = entry.styleId; const id = entry.styleId;
const box = $('#confirm'); const box = $('#confirm');
const cancel = $('[data-cmd="cancel"]');
box.dataset.display = true; box.dataset.display = true;
box.style.cssText = ''; box.style.cssText = '';
$('b', box).textContent = $('.style-name', entry).textContent; $('b', box).textContent = $('.style-name', entry).textContent;
@ -368,7 +418,7 @@ Object.assign(handleEvent, {
$('[data-cmd="cancel"]', box).onclick = () => confirm(false); $('[data-cmd="cancel"]', box).onclick = () => confirm(false);
window.onkeydown = event => { window.onkeydown = event => {
const keyCode = event.keyCode || event.which; const keyCode = event.keyCode || event.which;
if (!event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey if (document.activeElement !== cancel && !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey
&& (keyCode === 13 || keyCode === 27)) { && (keyCode === 13 || keyCode === 27)) {
event.preventDefault(); event.preventDefault();
confirm(keyCode === 13); confirm(keyCode === 13);

View File

@ -34,11 +34,11 @@ class Reporter {
* verification back to the main API. * verification back to the main API.
* @class Reporter * @class Reporter
* @constructor * @constructor
* @param {String[]} lines The text lines of the source. * @param {String[]} lines - The text lines of the source.
* @param {Object} ruleset The set of rules to work with, including if * @param {Object} ruleset - The set of rules to work with, including if
* they are errors or warnings. * they are errors or warnings.
* @param {Object} explicitly allowed lines * @param {Object} allow - explicitly allowed lines
* @param {[][]} ingore list of line ranges to be ignored * @param {[][]} ingore - list of line ranges to be ignored
*/ */
constructor(lines, ruleset, allow, ignore) { constructor(lines, ruleset, allow, ignore) {
this.messages = []; this.messages = [];
@ -126,6 +126,9 @@ var CSSLint = (() => {
}; };
const rules = []; const rules = [];
// previous CSSLint overrides are used to decide whether the parserlib's cache should be reset
let prevOverrides;
return Object.assign(new parserlib.util.EventTarget(), { return Object.assign(new parserlib.util.EventTarget(), {
addRule(rule) { addRule(rule) {
@ -193,8 +196,13 @@ var CSSLint = (() => {
rules[id] && rules[id] &&
rules[id].init(parser, reporter)); rules[id].init(parser, reporter));
// TODO: when ruleset is unchanged we can try to invalidate only line ranges in 'allow' and 'ignore'
const newOvr = [ruleset, allow, ignore];
const reuseCache = !prevOverrides || JSON.stringify(prevOverrides) === JSON.stringify(newOvr);
prevOverrides = newOvr;
try { try {
parser.parse(text, {reuseCache: true}); parser.parse(text, {reuseCache});
} catch (ex) { } catch (ex) {
reporter.error('Fatal error, cannot continue: ' + ex.message, ex.line, ex.col, {}); reporter.error('Fatal error, cannot continue: ' + ex.message, ex.line, ex.col, {});
} }
@ -219,17 +227,50 @@ var CSSLint = (() => {
}, },
}); });
// Example 1:
/* csslint ignore:start */
// the chunk of code where errors won't be reported
// the chunk's start is hardwired to the line of the opening comment
// the chunk's end is hardwired to the line of the closing comment
/* csslint ignore:end */
// Example 2:
/* csslint allow:rulename1,rulename2,... */
// allows to break the specified rules on the next single line of code
// Example 3:
/* csslint rulename1 */
/* csslint rulename2:N */
/* csslint rulename3:N, rulename4:N */
// entire code is affected;
// comments futher down the code extend/override previous comments of this kind
// values for N:
// "2" or "true" means "error"
// "1" or nothing means "warning" - note in this case ":" can also be omitted
// "0" or "false" means "ignore"
// (the quotes are added here for convenience, don't put them in the actual comments)
function applyEmbeddedOverrides(text, ruleset, allow, ignore) { function applyEmbeddedOverrides(text, ruleset, allow, ignore) {
let ignoreStart = null; let ignoreStart = null;
let ignoreEnd = null; let ignoreEnd = null;
let lineno = 0; let lineno = 0;
let eol = -1;
let m;
for (let eol = 0, m; (m = RX_EMBEDDED.exec(text)); lineno++) { while ((m = RX_EMBEDDED.exec(text))) {
eol = (text.indexOf('\n', eol) + 1 || text.length + 1) - 1; // account for the lines between the previous and current match
if (eol < m.index) continue; while (eol <= m.index) {
eol = text.indexOf('\n', eol + 1);
if (eol < 0) eol = text.length;
lineno++;
}
const ovr = m[1].toLowerCase(); const ovr = m[1].toLowerCase();
const cmd = ovr.split(':', 1); const cmd = ovr.split(':', 1)[0];
const i = cmd.length + 1; const i = cmd.length + 1;
switch (cmd.trim()) { switch (cmd.trim()) {
@ -246,15 +287,13 @@ var CSSLint = (() => {
} }
case 'ignore': case 'ignore':
if (ovr.lastIndexOf('start', i) > 0) { if (ovr.includes('start')) {
if (ignoreStart === null) { ignoreStart = ignoreStart || lineno;
ignoreStart = lineno;
}
break; break;
} }
if (ovr.lastIndexOf('end', i) > 0) { if (ovr.includes('end')) {
ignoreEnd = lineno; ignoreEnd = lineno;
if (ignoreStart !== null && ignoreEnd !== null) { if (ignoreStart && ignoreEnd) {
ignore.push([ignoreStart, ignoreEnd]); ignore.push([ignoreStart, ignoreEnd]);
ignoreStart = ignoreEnd = null; ignoreStart = ignoreEnd = null;
} }
@ -273,7 +312,7 @@ var CSSLint = (() => {
} }
// Close remaining ignore block, if any // Close remaining ignore block, if any
if (ignoreStart !== null) { if (ignoreStart) {
ignore.push([ignoreStart, lineno]); ignore.push([ignoreStart, lineno]);
} }
} }

View File

@ -194,6 +194,7 @@ self.parserlib = (() => {
'border-bottom-right-radius': '<x-one-radius>', 'border-bottom-right-radius': '<x-one-radius>',
'border-bottom-style': '<border-style>', 'border-bottom-style': '<border-style>',
'border-bottom-width': '<border-width>', 'border-bottom-width': '<border-width>',
'border-boundary': 'none | parent | display',
'border-inline-color': '<color>{1,2}', 'border-inline-color': '<color>{1,2}',
'border-inline-end': '<border-shorthand>', 'border-inline-end': '<border-shorthand>',
'border-inline-end-color': '<color>', 'border-inline-end-color': '<color>',
@ -467,6 +468,7 @@ self.parserlib = (() => {
'marquee-speed': 1, 'marquee-speed': 1,
'marquee-style': 1, 'marquee-style': 1,
'mask': 1, 'mask': 1,
'mask-image': '[ none | <image> | <uri> ]#',
'max-height': 'none | <width-height>', 'max-height': 'none | <width-height>',
'max-width': 'none | <width-height>', 'max-width': 'none | <width-height>',
'min-height': 'auto | <width-height>', 'min-height': 'auto | <width-height>',
@ -496,7 +498,9 @@ self.parserlib = (() => {
'outline-offset': '<length>', 'outline-offset': '<length>',
'outline-style': '<border-style> | auto', 'outline-style': '<border-style> | auto',
'outline-width': '<border-width>', 'outline-width': '<border-width>',
'overflow': '<overflow>', 'overflow': '<overflow>{1,2}',
'overflow-block': '<overflow>',
'overflow-inline': '<overflow>',
'overflow-style': 1, 'overflow-style': 1,
'overflow-wrap': 'normal | break-word', 'overflow-wrap': 'normal | break-word',
'overflow-x': '<overflow>', 'overflow-x': '<overflow>',
@ -549,7 +553,7 @@ self.parserlib = (() => {
'rest-before': 1, 'rest-before': 1,
'richness': 1, 'richness': 1,
'right': '<width>', 'right': '<width>',
'rotate': 'none | <number>{3}? <angle>', 'rotate': 'none | [ x | y | z | <number>{3} ]? && <angle>',
'rotation': 1, 'rotation': 1,
'rotation-point': 1, 'rotation-point': 1,
'row-gap': '<row-gap>', 'row-gap': '<row-gap>',
@ -560,6 +564,7 @@ self.parserlib = (() => {
// S // S
'scale': 'none | <number>{1,3}', 'scale': 'none | <number>{1,3}',
'shape-inside': 'auto | outside-shape | [ <basic-shape> || shape-box ] | <image> | display',
'shape-rendering': 'auto | optimizeSpeed | crispEdges | geometricPrecision', 'shape-rendering': 'auto | optimizeSpeed | crispEdges | geometricPrecision',
'size': 1, 'size': 1,
'speak': 'normal | none | spell-out', 'speak': 'normal | none | spell-out',
@ -598,15 +603,18 @@ self.parserlib = (() => {
'text-decoration-skip': 'none | [ objects || [ spaces | [ leading-spaces || trailing-spaces ] ] || ' + 'text-decoration-skip': 'none | [ objects || [ spaces | [ leading-spaces || trailing-spaces ] ] || ' +
'edges || box-decoration ]', 'edges || box-decoration ]',
'text-decoration-style': '<text-decoration-style>', 'text-decoration-style': '<text-decoration-style>',
'text-emphasis': 1, 'text-emphasis': '<text-emphasis-style> || <color>',
'text-emphasis-style': '<text-emphasis-style>',
'text-emphasis-position': '[ over | under ] && [ right | left ]?',
'text-height': 1, 'text-height': 1,
'text-indent': '<length> | <percentage>', 'text-indent': '<length> | <percentage>',
'text-justify': 'auto | none | inter-word | inter-ideograph | inter-cluster | distribute | kashida', 'text-justify': 'auto | none | inter-word | inter-ideograph | inter-cluster | distribute | kashida',
'text-outline': 1, 'text-outline': 1,
'text-overflow': 'clip | ellipsis', 'text-overflow': 'clip | ellipsis',
'text-rendering': 'auto | optimizeSpeed | optimizeLegibility | geometricPrecision', 'text-rendering': 'auto | optimizeSpeed | optimizeLegibility | geometricPrecision',
'text-shadow': 'none | [ [ <color> && <length>{2,3} ] | <length>{2,3} ]#', 'text-shadow': 'none | [ <color>? && <length>{2,3} ]#',
'text-transform': 'capitalize | uppercase | lowercase | none', 'text-transform': 'capitalize | uppercase | lowercase | none',
'text-underline-position': 'auto | [ under || [ left | right ] ]',
'text-wrap': 'normal | none | avoid', 'text-wrap': 'normal | none | avoid',
'top': '<width>', 'top': '<width>',
'touch-action': 'auto | none | pan-x | pan-y | pan-left | pan-right | pan-up | pan-down | manipulation', 'touch-action': 'auto | none | pan-x | pan-y | pan-left | pan-right | pan-up | pan-down | manipulation',
@ -689,7 +697,7 @@ self.parserlib = (() => {
'<basic-shape>': 'inset() | circle() | ellipse() | polygon()', '<basic-shape>': 'inset() | circle() | ellipse() | polygon()',
'<bg-image>': '<image> | <gradient> | none', '<bg-image>': '<image> | none',
'<blend-mode>': 'normal | multiply | screen | overlay | darken | lighten | color-dodge | ' + '<blend-mode>': 'normal | multiply | screen | overlay | darken | lighten | color-dodge | ' +
'color-burn | hard-light | soft-light | difference | exclusion | hue | ' + 'color-burn | hard-light | soft-light | difference | exclusion | hue | ' +
@ -773,7 +781,7 @@ self.parserlib = (() => {
return this['<ident>'](part) && !this['<generic-family>'](part); return this['<ident>'](part) && !this['<generic-family>'](part);
}, },
'<image>': '<uri>', '<image>': '<uri> | <gradient> | cross-fade()',
'<inflexible-breadth>': '<length-percentage> | min-content | max-content | auto', '<inflexible-breadth>': '<length-percentage> | min-content | max-content | auto',
@ -832,7 +840,7 @@ self.parserlib = (() => {
return this['<number>'](part) && part.value >= 0 && part.value <= 1; return this['<number>'](part) && part.value >= 0 && part.value <= 1;
}, },
'<overflow>': 'visible | hidden | scroll | auto', '<overflow>': 'visible | hidden | clip | scroll | auto',
'<overflow-position>': 'unsafe | safe', '<overflow-position>': 'unsafe | safe',
@ -882,12 +890,15 @@ self.parserlib = (() => {
if (part.tokenType === Tokens.USO_VAR) return true; if (part.tokenType === Tokens.USO_VAR) return true;
if (part.type !== 'function' || !part.expr) return false; if (part.type !== 'function' || !part.expr) return false;
const subparts = part.expr.parts; const subparts = part.expr.parts;
return subparts.length && if (!subparts.length) return false;
lower(part.name) === 'var' && const name = lower(part.name);
subparts[0].type === 'custom-property' && ( return (
subparts.length === 1 || name === 'var' && subparts[0].type === 'custom-property' ||
subparts[1].text === ',' name === 'env' && subparts[0].type === 'identifier'
); ) && (
subparts.length === 1 ||
subparts[1].text === ','
);
}, },
'<width>': '<length> | <percentage> | auto', '<width>': '<length> | <percentage> | auto',
@ -1039,18 +1050,16 @@ self.parserlib = (() => {
'<hsl-color>': '[ <number> | <angle> ] <percentage>{2} [ / <nonnegative-number-or-percentage> ]? | ' + '<hsl-color>': '[ <number> | <angle> ] <percentage>{2} [ / <nonnegative-number-or-percentage> ]? | ' +
'[ <number> | <angle> ] , <percentage>#{2} [ , <nonnegative-number-or-percentage> ]?', '[ <number> | <angle> ] , <percentage>#{2} [ , <nonnegative-number-or-percentage> ]?',
// inset? && [ <length>{2,4} && <color>? ] '<shadow>': 'inset? && [ <length>{2,4} && <color>? ]',
'<shadow>': Matcher =>
Matcher.many(
[true],
Matcher.cast('<length>').braces(2, 4),
'inset',
'<color>'),
'<single-timing-function>': 'linear | <cubic-bezier-timing-function> | <step-timing-function> | frames()', '<single-timing-function>': 'linear | <cubic-bezier-timing-function> | <step-timing-function> | frames()',
'<text-decoration-line>': 'none | [ underline || overline || line-through || blink ]', '<text-decoration-line>': 'none | [ underline || overline || line-through || blink ]',
'<text-emphasis-style>': 'none | ' +
'[ [ filled | open ] || [ dot | circle | double-circle | triangle | sesame ] ] | ' +
'<string>',
'<track-list>': '[ <line-names>? [ <track-size> | <track-repeat> ] ]+ <line-names>?', '<track-list>': '[ <line-names>? [ <track-size> | <track-repeat> ] ]+ <line-names>?',
'<track-repeat>': 'repeat( [ <positive-integer> ] , [ <line-names>? <track-size> ]+ <line-names>? )', '<track-repeat>': 'repeat( [ <positive-integer> ] , [ <line-names>? <track-size> ]+ <line-names>? )',
@ -1408,6 +1417,7 @@ self.parserlib = (() => {
{name: 'NOT'}, {name: 'NOT'},
{name: 'ANY', text: ['any', '-webkit-any', '-moz-any']}, {name: 'ANY', text: ['any', '-webkit-any', '-moz-any']},
{name: 'MATCHES'}, {name: 'MATCHES'},
{name: 'IS'},
/* /*
* Defined in CSS3 Paged Media * Defined in CSS3 Paged Media
@ -1881,7 +1891,8 @@ self.parserlib = (() => {
const p = required === false ? Matcher.prec.OROR : Matcher.prec.ANDAND; const p = required === false ? Matcher.prec.OROR : Matcher.prec.ANDAND;
const s = ms.map((m, i) => { const s = ms.map((m, i) => {
if (required !== false && !required[i]) { if (required !== false && !required[i]) {
return m.toString(Matcher.prec.MOD) + '?'; const str = m.toString(Matcher.prec.MOD);
return str.endsWith('?') ? str : str + '?';
} }
return m.toString(p); return m.toString(p);
}).join(required === false ? ' || ' : ' && '); }).join(required === false ? ' || ' : ' && ');
@ -1919,10 +1930,22 @@ self.parserlib = (() => {
function andand() { function andand() {
// andand = seq ( " && " seq)* // andand = seq ( " && " seq)*
const m = [seq()]; const m = [seq()];
let reqPrev = !isOptional(m[0]);
const required = [reqPrev];
while (reader.readMatch(' && ')) { while (reader.readMatch(' && ')) {
m.push(seq()); const item = seq();
const req = !isOptional(item);
// Matcher.many apparently can't handle optional items first
if (req && !reqPrev) {
m.unshift(item);
required.unshift(req);
} else {
m.push(item);
required.push(req);
reqPrev = req;
}
} }
return m.length === 1 ? m[0] : Matcher.andand.apply(Matcher, m); return m.length === 1 ? m[0] : Matcher.many(required, ...m);
} }
function seq() { function seq() {
@ -1977,6 +2000,10 @@ self.parserlib = (() => {
} }
return result; return result;
} }
function isOptional(item) {
return !Array.isArray(item.options) && item.toString().endsWith('?');
}
})(); })();
//endregion //endregion
@ -2663,7 +2690,7 @@ self.parserlib = (() => {
known.add(value.text); known.add(value.text);
function throwEndExpected(token, force) { function throwEndExpected(token, force) {
if (force || token.name !== 'var' || token.type !== 'function') { if (force || (token.name !== 'var' && token.name !== 'env') || token.type !== 'function') {
throw new ValidationError(`Expected end of value but found '${token.text}'.`, token); throw new ValidationError(`Expected end of value but found '${token.text}'.`, token);
} }
} }
@ -3012,11 +3039,13 @@ self.parserlib = (() => {
/* /*
* Potential tokens: * Potential tokens:
* - ANY * - ANY
* - IS
* - MATCHES
* - NOT * - NOT
* - CHAR * - CHAR
*/ */
case ':': case ':':
return this.notOrAnyOrMatchesToken(c, pos); return this.notOrIsToken(c, pos);
/* /*
* Potential tokens: * Potential tokens:
@ -3224,16 +3253,20 @@ self.parserlib = (() => {
} }
// NOT // NOT
// IS
// ANY // ANY
// MATCHES // MATCHES
// CHAR // CHAR
notOrAnyOrMatchesToken(first, pos) { notOrIsToken(first, pos) {
// first is always ':'
const reader = this._reader; const reader = this._reader;
const func = reader.readMatch(/(not|(-(moz|webkit)-)?any|matches)\(/iy); const func = reader.readMatch(/(not|is|(-(moz|webkit)-)?(any|matches))\(/iy);
if (func) { if (func) {
const lcase = func[0].toLowerCase();
const type = const type =
func.startsWith('n') || func.startsWith('N') ? Tokens.NOT : lcase === 'n' ? Tokens.NOT :
func.startsWith('m') || func.startsWith('M') ? Tokens.MATCHES : Tokens.ANY; lcase === 'i' ? Tokens.IS :
lcase === 'm' ? Tokens.MATCHES : Tokens.ANY;
return this.createToken(type, first + func, pos); return this.createToken(type, first + func, pos);
} }
return this.charToken(first, pos); return this.charToken(first, pos);
@ -4239,14 +4272,25 @@ self.parserlib = (() => {
_viewport() { _viewport() {
const stream = this._tokenStream; const stream = this._tokenStream;
stream.mustMatch(Tokens.VIEWPORT_SYM); const start = stream.mustMatch(Tokens.VIEWPORT_SYM);
this.fire('startviewport'); // only viewport-fit is allowed but we're reusing MediaQuery syntax unit,
// and accept anything for the sake of simplicity since the spec isn't yet final:
// https://drafts.csswg.org/css-round-display/#extending-viewport-rule
const descriptors = this._mediaQueryList();
this.fire({
type: 'startviewport',
descriptors,
}, start);
this._ws(); this._ws();
this._readDeclarations(); this._readDeclarations();
this.fire('endviewport'); this.fire({
type: 'endviewport',
descriptors,
});
} }
_document() { _document() {
@ -4664,13 +4708,13 @@ self.parserlib = (() => {
return value.length ? value : null; return value.length ? value : null;
} }
_anyOrMatches() { _is() {
const stream = this._tokenStream; const stream = this._tokenStream;
if (!stream.match([Tokens.ANY, Tokens.MATCHES])) return null; if (!stream.match([Tokens.IS, Tokens.ANY, Tokens.MATCHES])) return null;
let arg; let arg;
const start = stream._token; const start = stream._token;
const type = start.type === Tokens.ANY ? 'any' : 'matches'; const type = lower(Tokens[start.type].name);
const value = const value =
start.value + start.value +
this._ws() + this._ws() +
@ -5380,6 +5424,7 @@ self.parserlib = (() => {
[Tokens.MEDIA_SYM, Parser.prototype._media], [Tokens.MEDIA_SYM, Parser.prototype._media],
[Tokens.SUPPORTS_SYM, Parser.prototype._supports], [Tokens.SUPPORTS_SYM, Parser.prototype._supports],
[Tokens.DOCUMENT_SYM, Parser.prototype._documentMisplaced], [Tokens.DOCUMENT_SYM, Parser.prototype._documentMisplaced],
[Tokens.VIEWPORT_SYM, Parser.prototype._viewport],
]), ]),
media: new Map([ media: new Map([
@ -5396,8 +5441,9 @@ self.parserlib = (() => {
[Tokens.DOT, Parser.prototype._class], [Tokens.DOT, Parser.prototype._class],
[Tokens.LBRACKET, Parser.prototype._attrib], [Tokens.LBRACKET, Parser.prototype._attrib],
[Tokens.COLON, Parser.prototype._pseudo], [Tokens.COLON, Parser.prototype._pseudo],
[Tokens.ANY, Parser.prototype._anyOrMatches], [Tokens.IS, Parser.prototype._is],
[Tokens.MATCHES, Parser.prototype._anyOrMatches], [Tokens.ANY, Parser.prototype._is],
[Tokens.MATCHES, Parser.prototype._is],
[Tokens.NOT, Parser.prototype._negation], [Tokens.NOT, Parser.prototype._negation],
]), ]),
}; };