Skip to content

Commit e716444

Browse files
committed
add [x] override style to included sites, #1832
+ rename "custom" to "personal" for a stronger signal that this is not a part of the style + rename "exclusions" to "glob" in functions that are universal
1 parent 92db0b9 commit e716444

File tree

12 files changed

+133
-99
lines changed

12 files changed

+133
-99
lines changed

src/.types.d.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ declare interface StyleObj {
3232
customName?: string;
3333
exclusions?: string[];
3434
inclusions?: string[];
35+
/** override style's own specifiers */
36+
overridden?: boolean;
3537
installDate?: number;
3638
installationUrl?: string;
3739
md5Url?: string;
@@ -67,19 +69,19 @@ declare interface StyleSection {
6769

6870
type TabCache = {[tabId:string]: TabCacheEntry};
6971

72+
declare interface TabCacheEntry {
73+
id: number;
74+
incognito: boolean;
75+
nonce: Object;
76+
url: Object;
77+
styleIds: StyleIdsFrameMap;
78+
}
79+
7080
declare interface StyleIdsFrameMap {
7181
url: string;
7282
styleIds: {[frameId: string]: number[]};
7383
}
7484

75-
declare interface TabCacheEntry {
76-
id: number;
77-
incognito: boolean;
78-
nonce: Object;
79-
url: Object;
80-
styleIds: StyleIdsFrameMap;
81-
}
82-
8385
declare namespace Injection {
8486
interface Response {
8587
cfg: Config;
@@ -138,6 +140,18 @@ declare interface MatchQuery {
138140
urlWithoutParams?: string;
139141
}
140142

143+
declare interface MatchUrlResult {
144+
empty: boolean;
145+
sloppy: boolean;
146+
style: StyleObj;
147+
// optionals
148+
excluded?: boolean;
149+
excludedScheme?: boolean;
150+
included?: boolean;
151+
/** not included and not matching the original sites */
152+
overridden?: boolean;
153+
}
154+
141155
declare interface UsercssData {
142156
name: string;
143157
namespace: string;

src/_locales/en/messages.json

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,8 +1564,9 @@
15641564
"message": "Enabled",
15651565
"description": "Label for the enabled state of styles"
15661566
},
1567-
"styleExcludeLabel": {
1568-
"message": "Custom excluded sites"
1567+
"styleForceApplied": {
1568+
"message": "Force-applied via \"<INC>\" in style settings",
1569+
"description": "Tooltip in the popup for styles, <INC> = translation for Personal included sites"
15691570
},
15701571
"styleFromMozillaFormatError": {
15711572
"message": "Failed to import from Mozilla format",
@@ -1575,9 +1576,6 @@
15751576
"message": "Paste the Mozilla-format code",
15761577
"description": "Prompt in the dialog displayed after clicking 'Import from Mozilla format' button"
15771578
},
1578-
"styleIncludeLabel": {
1579-
"message": "Custom included sites"
1580-
},
15811579
"styleInjectionImportance": {
15821580
"message": "Toggle style's importance"
15831581
},
@@ -1640,6 +1638,10 @@
16401638
"styleName": {
16411639
"message": "Style name"
16421640
},
1641+
"styleNotAppliedOverridden": {
1642+
"message": "Your \"<INC>\" in style settings override style's original sites",
1643+
"description": "Tooltip in the popup for styles, <INC> = translation for Personal included sites"
1644+
},
16431645
"styleNotAppliedRegexpProblemTooltip": {
16441646
"message": "Style was not applied due to its incorrect usage of 'regexp()'",
16451647
"description": "Tooltip in the popup for styles that were not applied at all"
@@ -1699,6 +1701,19 @@
16991701
"message": "Style settings",
17001702
"description": "Label/title for style settings dialog"
17011703
},
1704+
"styleSitesExclude": {
1705+
"message": "Personal excluded sites"
1706+
},
1707+
"styleSitesInclude": {
1708+
"message": "Personal included sites"
1709+
},
1710+
"styleSitesIncludeNote": {
1711+
"message": "Sites where this style is enabled on your personal device, either added to or used instead of the sites defined by the style depending on the checkbox. Useful for external styles that you shouldn't edit in order to keep them auto-updatable. Note that in multi-sectioned styles all sections are enabled for your added site, even those that were meant for specific pages of the original site."
1712+
},
1713+
"styleSitesIncludeOvr": {
1714+
"message": "override style",
1715+
"description": "Checkbox in the editor that reads like a continuation of the previous label: `Personal included sites` [x] `override original`, i.e. enabling it will cause personal included sites to override the style"
1716+
},
17021717
"styleToMozillaFormatHelp": {
17031718
"message": "The Mozilla format of the code can be submitted to userstyles.org and used with the classic Stylish for Firefox",
17041719
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format"

src/background/color-scheme.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ prefs.subscribe(kSTATE, (_, val) => {
6464
}, true);
6565

6666
/** @param {StyleObj} _ */
67-
export function shouldIncludeStyle({preferScheme: ps}) {
67+
export function themeAllowsStyle({preferScheme: ps}) {
6868
return prefState === kNever ||
6969
ps !== kDark && ps !== kLight ||
7070
isDark === (ps === kDark);

src/background/style-manager/cache-builder.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {styleCodeEmpty} from '@/js/sections-util';
2+
import {themeAllowsStyle} from '../color-scheme';
23
import cacheData, * as styleCache from './cache';
3-
import {urlMatchSection, urlMatchStyle} from './matcher';
4+
import {urlMatchGlob, urlMatchSection} from './matcher';
45
import {dataMap} from './util';
56

67
/** @param {StyleObj} style
@@ -18,7 +19,7 @@ export function buildCacheForStyle(style) {
1819
(cache.maybeMatch ??= new Set()).add(id);
1920
continue;
2021
}
21-
const code = getAppliedCode({url}, styleToApply);
22+
const code = styleToApply.enabled && getAppliedCode({url}, styleToApply);
2223
if (code) {
2324
updated.add(url);
2425
buildCacheEntry(cache, styleToApply, code);
@@ -65,16 +66,20 @@ function buildCacheEntry(entry, style, [idx, code]) {
6566
/** Get styles matching a URL, including sloppy regexps and excluded items.
6667
* @param {MatchQuery} query
6768
* @param {StyleObj} style
68-
* @return {?Array}
69+
* @return {[number[], string[]] | void}
6970
*/
7071
function getAppliedCode(query, style) {
71-
const result = urlMatchStyle(query, style);
72-
const isIncluded = result === 'included';
72+
let v;
73+
/** Make sure to use the same logic in getAppliedCode and getByUrl */
74+
const result = style.enabled &&
75+
themeAllowsStyle(style) &&
76+
(!(v = style.exclusions) || !v.length || v.some(urlMatchGlob, query)) &&
77+
(!(v = style.inclusions) || !v.length || -v.some(urlMatchGlob, query) || !style.overridden);
78+
if (!result)
79+
return;
80+
const isIncluded = result < 0;
7381
const code = [];
7482
const idx = [];
75-
if (!isIncluded && result !== true) {
76-
return;
77-
}
7883
let i = 0;
7984
for (const section of style.sections) {
8085
if ((isIncluded || urlMatchSection(query, section) === true)

src/background/style-manager/index.js

Lines changed: 36 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ import cacheData, * as styleCache from './cache';
1919
import {buildCache} from './cache-builder';
2020
import './init';
2121
import {onBeforeSave, onSaved} from './fixer';
22-
import {urlMatchSection, urlMatchStyle} from './matcher';
22+
import {urlMatchGlob, urlMatchSection} from './matcher';
2323
import {
24-
broadcastStyleUpdated, calcRemoteId, dataMap, getById, getByUuid,
25-
mergeWithMapped, order, orderWrap, setOrderImpl,
24+
broadcastStyleUpdated, calcRemoteId, dataMap, getById, getByUuid, mergeWithMapped, order,
25+
orderWrap, setOrderImpl,
2626
} from './util';
2727

2828
export * from '../style-search-db';
@@ -33,7 +33,8 @@ export async function config(id, prop, value) {
3333
const style = Object.assign({}, getById(id));
3434
const d = dataMap.get(id);
3535
style[prop] = (d.preview || {})[prop] = value;
36-
if (prop === 'inclusions' || prop === 'exclusions') styleCache.clear();
36+
if (prop === 'inclusions' || prop === 'overridden' || prop === 'exclusions')
37+
styleCache.clear();
3738
await save(style, 'config');
3839
}
3940

@@ -80,56 +81,46 @@ export function getAllOrdered(keys) {
8081
: res;
8182
}
8283

83-
/** @returns {StylesByUrlResult[]} */
84-
export function getByUrl(url, id = null) {
84+
/**
85+
* @param {string} url
86+
* @param {number} [id]
87+
* @returns {MatchUrlResult[]}
88+
*/
89+
export function getByUrl(url, id) {
8590
// FIXME: do we want to cache this? Who would like to open popup rapidly
8691
// or search the DB with the same URL?
87-
const result = [];
92+
const results = [];
8893
const query = {url};
8994
for (const {style} of id ? [dataMap.get(id)].filter(Boolean) : dataMap.values()) {
95+
let ovr;
96+
let matching;
97+
/** Make sure to use the same logic in getAppliedCode and getByUrl */
98+
const res = {
99+
excluded: (ovr = style.exclusions) && ovr.some(urlMatchGlob, query),
100+
excludedScheme: !colorScheme.themeAllowsStyle(style),
101+
included: matching = (ovr = style.inclusions) && ovr.some(urlMatchGlob, query),
102+
overridden: !matching && style.overridden && ovr?.length,
103+
};
104+
const isIncluded = matching;
90105
let empty = true;
91-
let excluded = false;
92-
let excludedScheme = false;
93-
let included = false;
94106
let sloppy = false;
95-
let sectionMatched = false;
96-
let match = urlMatchStyle(query, style);
97-
// TODO: enable this when the function starts returning false
98-
// if (match === false) {
99-
// continue;
100-
// }
101-
if (match === 'included') {
102-
included = true;
103-
}
104-
if (match === 'excluded') {
105-
excluded = true;
106-
}
107-
if (match === 'excludedScheme') {
108-
excludedScheme = true;
109-
}
110-
for (const section of style.sections) {
111-
match = urlMatchSection(query, section, true);
112-
if (match) {
113-
if (match === 'sloppy') {
114-
sloppy = true;
115-
}
116-
sectionMatched = true;
117-
if (empty) empty = styleCodeEmpty(section);
118-
}
107+
for (let arr = style.sections, i = 0; i < arr.length && (!matching || empty || !sloppy); i++) {
108+
const sec = arr[i];
109+
const secMatch = isIncluded || urlMatchSection(query, sec, true);
110+
if (!secMatch)
111+
continue;
112+
matching = true;
113+
sloppy ||= secMatch === 'sloppy';
114+
empty &&= styleCodeEmpty(sec);
119115
}
120-
if (sectionMatched || included) {
121-
result.push(/** @namespace StylesByUrlResult */ {
122-
empty,
123-
excluded,
124-
excludedScheme,
125-
included,
126-
sectionMatched,
127-
sloppy,
128-
style: getCore({id: style.id}),
129-
});
116+
if (matching) {
117+
res.empty = empty;
118+
res.sloppy = sloppy;
119+
res.style = getCore({id: style.id});
120+
results.push(res);
130121
}
131122
}
132-
return result;
123+
return results;
133124
}
134125

135126
/**

src/background/style-manager/matcher.js

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import {styleCodeEmpty} from '@/js/sections-util';
22
import {ownRoot} from '@/js/urls';
33
import {globAsRegExpStr, tryRegExp, tryURL} from '@/js/util';
4-
import * as colorScheme from '../color-scheme';
54

65
const BAD_MATCHER = {test: () => false};
76
const EXT_RE = /\bextension\b/;
87
const compileRe = createCompiler(text => `^(${text})$`);
98
const compileSloppyRe = createCompiler(text => `^${text}$`);
10-
const compileExclusion = createCompiler(buildExclusion);
9+
const compileGlob = createCompiler(buildGlobRe);
1110

12-
function buildExclusion(text) {
11+
function buildGlobRe(text) {
1312
// match pattern
1413
const match = text.match(/^(\*|[\w-]+):\/\/(\*\.)?([\w.]+\/.*)/);
1514
if (!match) {
@@ -35,27 +34,6 @@ function createCompiler(compile) {
3534
};
3635
}
3736

38-
function urlMatchExclusion(e) {
39-
return compileExclusion(e).test(this.urlWithoutParams ??= this.url.split(/[?#]/, 1)[0]);
40-
}
41-
42-
export function urlMatchStyle(query, style) {
43-
let ovr;
44-
if ((ovr = style.exclusions) && ovr.some(urlMatchExclusion, query)) {
45-
return 'excluded';
46-
}
47-
if (!style.enabled) {
48-
return 'disabled';
49-
}
50-
if (!colorScheme.shouldIncludeStyle(style)) {
51-
return 'excludedScheme';
52-
}
53-
if ((ovr = style.inclusions) && ovr.some(urlMatchExclusion, query)) {
54-
return 'included';
55-
}
56-
return true;
57-
}
58-
5937
export function urlMatchSection(query, section, skipEmptyGlobal) {
6038
let dd, ddL, pp, ppL, rr, rrL, uu, uuL;
6139
if (
@@ -96,6 +74,11 @@ function urlMatchDomain(d) {
9674
_d[_d.length - d.length - 1] === '.' && _d.endsWith(d);
9775
}
9876

77+
/** @this {MatchQuery} */
78+
export function urlMatchGlob(e) {
79+
return compileGlob(e).test(this.urlWithoutParams ??= this.url.split(/[?#]/, 1)[0]);
80+
}
81+
9982
/** @this {MatchQuery} */
10083
function urlMatchPrefix(p) {
10184
return p && this.url.startsWith(p);

src/css/global.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,9 @@ summary {
324324
width: 0;
325325
flex: auto;
326326
}
327+
.ws-nowrap {
328+
white-space: nowrap;
329+
}
327330
.ellipsis {
328331
white-space: nowrap;
329332
overflow: hidden;

src/edit/settings.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
min-height: $height;
3232
max-height: 50vh;
3333
white-space: pre;
34+
font: inherit;
3435
&:placeholder-shown {
3536
resize: none;
3637
height: 1.75em;

src/edit/settings.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ function StyleSettings(ui) {
4343
const pendingSetters = new Map();
4444
const updaters = [
4545
initCheckbox(elUpd, 'updatable', tryURL(style.updateUrl).href),
46+
initCheckbox('#ss-overridden', 'overridden', false),
4647
initInput('#ss-update-url', 'updateUrl', '', {
4748
validate(el) {
4849
elUpd.disabled = !el.value || !el.validity.valid;

src/edit/style-settings.html

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,18 @@
2020
<input name="ss-scheme" type="radio" value="light">
2121
</label>
2222
</div>
23-
<label i18n="styleIncludeLabel">
23+
<div>
24+
<label for="ss-inclusions" i18n="styleSitesInclude"></label>
25+
<a class="icon" tabindex="0" data-cmd="note" i18n="title:styleSitesIncludeNote">
26+
<i class="i-info"></i>
27+
</a> <!--preserving space-->
28+
<label i18n="+styleSitesIncludeOvr" class="ws-nowrap">
29+
<input id="ss-overridden" type="checkbox">
30+
</label>
2431
<textarea id="ss-inclusions" spellcheck="false" class="w100"
2532
placeholder="*://www.aaa.com/*"></textarea>
26-
</label>
27-
<label i18n="styleExcludeLabel">
33+
</div>
34+
<label i18n="styleSitesExclude">
2835
<textarea id="ss-exclusions" spellcheck="false" class="w100"
2936
placeholder="*://www.aaa.com/*"></textarea>
3037
</label>

0 commit comments

Comments
 (0)