Skip to content

Commit 70a0de9

Browse files
committed
[mv3] Mind trusted-site directives when registering content scripts
1 parent f374c05 commit 70a0de9

File tree

7 files changed

+551
-354
lines changed

7 files changed

+551
-354
lines changed

platform/mv3/extension/js/background.js

Lines changed: 28 additions & 317 deletions
Original file line numberDiff line numberDiff line change
@@ -25,62 +25,41 @@
2525

2626
/******************************************************************************/
2727

28-
import { browser, dnr, i18n, runtime } from './ext.js';
29-
import { fetchJSON } from './fetch.js';
30-
import { getInjectableCount, registerInjectable } from './scripting-manager.js';
31-
import { parsedURLromOrigin } from './utils.js';
28+
import {
29+
browser,
30+
dnr,
31+
runtime,
32+
} from './ext.js';
33+
34+
import {
35+
CURRENT_CONFIG_BASE_RULE_ID,
36+
getRulesetDetails,
37+
getDynamicRules,
38+
defaultRulesetsFromLanguage,
39+
enableRulesets,
40+
getEnabledRulesetsStats,
41+
updateRegexRules,
42+
} from './ruleset-manager.js';
43+
44+
import {
45+
getInjectableCount,
46+
registerInjectable,
47+
} from './scripting-manager.js';
48+
49+
import {
50+
matchesTrustedSiteDirective,
51+
toggleTrustedSiteDirective,
52+
} from './trusted-sites.js';
3253

3354
/******************************************************************************/
3455

35-
const RULE_REALM_SIZE = 1000000;
36-
const REGEXES_REALM_START = 1000000;
37-
const REGEXES_REALM_END = REGEXES_REALM_START + RULE_REALM_SIZE;
38-
const TRUSTED_DIRECTIVE_BASE_RULE_ID = 8000000;
39-
const CURRENT_CONFIG_BASE_RULE_ID = 9000000;
40-
4156
const rulesetConfig = {
4257
version: '',
4358
enabledRulesets: [],
4459
};
4560

4661
/******************************************************************************/
4762

48-
let rulesetDetailsPromise;
49-
50-
function getRulesetDetails() {
51-
if ( rulesetDetailsPromise !== undefined ) {
52-
return rulesetDetailsPromise;
53-
}
54-
rulesetDetailsPromise = fetchJSON('/rulesets/ruleset-details').then(entries => {
55-
const map = new Map(
56-
entries.map(entry => [ entry.id, entry ])
57-
);
58-
return map;
59-
});
60-
return rulesetDetailsPromise;
61-
}
62-
63-
/******************************************************************************/
64-
65-
let dynamicRuleMapPromise;
66-
67-
function getDynamicRules() {
68-
if ( dynamicRuleMapPromise !== undefined ) {
69-
return dynamicRuleMapPromise;
70-
}
71-
dynamicRuleMapPromise = dnr.getDynamicRules().then(rules => {
72-
const map = new Map(
73-
rules.map(rule => [ rule.id, rule ])
74-
);
75-
console.log(`Dynamic rule count: ${map.size}`);
76-
console.log(`Available dynamic rule count: ${dnr.MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES - map.size}`);
77-
return map;
78-
});
79-
return dynamicRuleMapPromise;
80-
}
81-
82-
/******************************************************************************/
83-
8463
function getCurrentVersion() {
8564
return runtime.getManifest().version;
8665
}
@@ -134,276 +113,6 @@ async function saveRulesetConfig() {
134113

135114
/******************************************************************************/
136115

137-
async function updateRegexRules() {
138-
const [
139-
rulesetDetails,
140-
dynamicRules
141-
] = await Promise.all([
142-
getRulesetDetails(),
143-
dnr.getDynamicRules(),
144-
]);
145-
146-
// Avoid testing already tested regexes
147-
const validRegexSet = new Set(
148-
dynamicRules.filter(rule =>
149-
rule.condition?.regexFilter && true || false
150-
).map(rule =>
151-
rule.condition.regexFilter
152-
)
153-
);
154-
const allRules = [];
155-
const toCheck = [];
156-
157-
// Fetch regexes for all enabled rulesets
158-
const toFetch = [];
159-
for ( const details of rulesetDetails.values() ) {
160-
if ( details.enabled !== true ) { continue; }
161-
if ( details.rules.regexes === 0 ) { continue; }
162-
toFetch.push(fetchJSON(`/rulesets/${details.id}.regexes`));
163-
}
164-
const regexRulesets = await Promise.all(toFetch);
165-
166-
// Validate fetched regexes
167-
let regexRuleId = REGEXES_REALM_START;
168-
for ( const rules of regexRulesets ) {
169-
if ( Array.isArray(rules) === false ) { continue; }
170-
for ( const rule of rules ) {
171-
rule.id = regexRuleId++;
172-
const {
173-
regexFilter: regex,
174-
isUrlFilterCaseSensitive: isCaseSensitive
175-
} = rule.condition;
176-
allRules.push(rule);
177-
toCheck.push(
178-
validRegexSet.has(regex)
179-
? { isSupported: true }
180-
: dnr.isRegexSupported({ regex, isCaseSensitive })
181-
);
182-
}
183-
}
184-
185-
// Collate results
186-
const results = await Promise.all(toCheck);
187-
const newRules = [];
188-
for ( let i = 0; i < allRules.length; i++ ) {
189-
const rule = allRules[i];
190-
const result = results[i];
191-
if ( result instanceof Object && result.isSupported ) {
192-
newRules.push(rule);
193-
} else {
194-
console.info(`${result.reason}: ${rule.condition.regexFilter}`);
195-
}
196-
}
197-
console.info(
198-
`Rejected regex filters: ${allRules.length-newRules.length} out of ${allRules.length}`
199-
);
200-
201-
// Add validated regex rules to dynamic ruleset without affecting rules
202-
// outside regex rule realm.
203-
const dynamicRuleMap = await getDynamicRules();
204-
const newRuleMap = new Map(newRules.map(rule => [ rule.id, rule ]));
205-
const addRules = [];
206-
const removeRuleIds = [];
207-
for ( const oldRule of dynamicRuleMap.values() ) {
208-
if ( oldRule.id < REGEXES_REALM_START ) { continue; }
209-
if ( oldRule.id >= REGEXES_REALM_END ) { continue; }
210-
const newRule = newRuleMap.get(oldRule.id);
211-
if ( newRule === undefined ) {
212-
removeRuleIds.push(oldRule.id);
213-
dynamicRuleMap.delete(oldRule.id);
214-
} else if ( JSON.stringify(oldRule) !== JSON.stringify(newRule) ) {
215-
removeRuleIds.push(oldRule.id);
216-
addRules.push(newRule);
217-
dynamicRuleMap.set(oldRule.id, newRule);
218-
}
219-
}
220-
for ( const newRule of newRuleMap.values() ) {
221-
if ( dynamicRuleMap.has(newRule.id) ) { continue; }
222-
addRules.push(newRule);
223-
dynamicRuleMap.set(newRule.id, newRule);
224-
}
225-
if ( addRules.length !== 0 || removeRuleIds.length !== 0 ) {
226-
return dnr.updateDynamicRules({ addRules, removeRuleIds });
227-
}
228-
}
229-
230-
/******************************************************************************/
231-
232-
async function matchesTrustedSiteDirective(details) {
233-
const url = parsedURLromOrigin(details.origin);
234-
if ( url === undefined ) { return false; }
235-
236-
const dynamicRuleMap = await getDynamicRules();
237-
let rule = dynamicRuleMap.get(TRUSTED_DIRECTIVE_BASE_RULE_ID);
238-
if ( rule === undefined ) { return false; }
239-
const domainSet = new Set(rule.condition.requestDomains);
240-
let hostname = url.hostname;
241-
for (;;) {
242-
if ( domainSet.has(hostname) ) { return true; }
243-
const pos = hostname.indexOf('.');
244-
if ( pos === -1 ) { break; }
245-
hostname = hostname.slice(pos+1);
246-
}
247-
248-
return false;
249-
}
250-
251-
async function addTrustedSiteDirective(details) {
252-
const url = parsedURLromOrigin(details.origin);
253-
if ( url === undefined ) { return false; }
254-
255-
const dynamicRuleMap = await getDynamicRules();
256-
let rule = dynamicRuleMap.get(TRUSTED_DIRECTIVE_BASE_RULE_ID);
257-
if ( rule !== undefined ) {
258-
rule.condition.initiatorDomains = undefined;
259-
if ( Array.isArray(rule.condition.requestDomains) === false ) {
260-
rule.condition.requestDomains = [];
261-
}
262-
}
263-
264-
if ( rule === undefined ) {
265-
rule = {
266-
id: TRUSTED_DIRECTIVE_BASE_RULE_ID,
267-
action: {
268-
type: 'allowAllRequests',
269-
},
270-
condition: {
271-
requestDomains: [ url.hostname ],
272-
resourceTypes: [ 'main_frame' ],
273-
},
274-
priority: TRUSTED_DIRECTIVE_BASE_RULE_ID,
275-
};
276-
dynamicRuleMap.set(TRUSTED_DIRECTIVE_BASE_RULE_ID, rule);
277-
} else if ( rule.condition.requestDomains.includes(url.hostname) === false ) {
278-
rule.condition.requestDomains.push(url.hostname);
279-
}
280-
281-
await dnr.updateDynamicRules({
282-
addRules: [ rule ],
283-
removeRuleIds: [ TRUSTED_DIRECTIVE_BASE_RULE_ID ],
284-
});
285-
286-
return true;
287-
}
288-
289-
async function removeTrustedSiteDirective(details) {
290-
const url = parsedURLromOrigin(details.origin);
291-
if ( url === undefined ) { return false; }
292-
293-
const dynamicRuleMap = await getDynamicRules();
294-
let rule = dynamicRuleMap.get(TRUSTED_DIRECTIVE_BASE_RULE_ID);
295-
if ( rule === undefined ) { return false; }
296-
rule.condition.initiatorDomains = undefined;
297-
if ( Array.isArray(rule.condition.requestDomains) === false ) {
298-
rule.condition.requestDomains = [];
299-
}
300-
301-
const domainSet = new Set(rule.condition.requestDomains);
302-
const beforeCount = domainSet.size;
303-
let hostname = url.hostname;
304-
for (;;) {
305-
domainSet.delete(hostname);
306-
const pos = hostname.indexOf('.');
307-
if ( pos === -1 ) { break; }
308-
hostname = hostname.slice(pos+1);
309-
}
310-
311-
if ( domainSet.size === beforeCount ) { return false; }
312-
313-
if ( domainSet.size === 0 ) {
314-
dynamicRuleMap.delete(TRUSTED_DIRECTIVE_BASE_RULE_ID);
315-
await dnr.updateDynamicRules({
316-
removeRuleIds: [ TRUSTED_DIRECTIVE_BASE_RULE_ID ]
317-
});
318-
return false;
319-
}
320-
321-
rule.condition.requestDomains = Array.from(domainSet);
322-
323-
await dnr.updateDynamicRules({
324-
addRules: [ rule ],
325-
removeRuleIds: [ TRUSTED_DIRECTIVE_BASE_RULE_ID ],
326-
});
327-
328-
return false;
329-
}
330-
331-
async function toggleTrustedSiteDirective(details) {
332-
return details.state
333-
? removeTrustedSiteDirective(details)
334-
: addTrustedSiteDirective(details);
335-
}
336-
337-
/******************************************************************************/
338-
339-
async function enableRulesets(ids) {
340-
const afterIds = new Set(ids);
341-
const beforeIds = new Set(await dnr.getEnabledRulesets());
342-
const enableRulesetIds = [];
343-
const disableRulesetIds = [];
344-
for ( const id of afterIds ) {
345-
if ( beforeIds.has(id) ) { continue; }
346-
enableRulesetIds.push(id);
347-
}
348-
for ( const id of beforeIds ) {
349-
if ( afterIds.has(id) ) { continue; }
350-
disableRulesetIds.push(id);
351-
}
352-
if ( enableRulesetIds.length !== 0 || disableRulesetIds.length !== 0 ) {
353-
return dnr.updateEnabledRulesets({ enableRulesetIds,disableRulesetIds });
354-
}
355-
}
356-
357-
async function getEnabledRulesetsStats() {
358-
const [
359-
rulesetDetails,
360-
ids,
361-
] = await Promise.all([
362-
getRulesetDetails(),
363-
dnr.getEnabledRulesets(),
364-
]);
365-
const out = [];
366-
for ( const id of ids ) {
367-
const ruleset = rulesetDetails.get(id);
368-
if ( ruleset === undefined ) { continue; }
369-
out.push(ruleset);
370-
}
371-
return out;
372-
}
373-
374-
async function defaultRulesetsFromLanguage() {
375-
const out = [ 'default' ];
376-
377-
const dropCountry = lang => {
378-
const pos = lang.indexOf('-');
379-
if ( pos === -1 ) { return lang; }
380-
return lang.slice(0, pos);
381-
};
382-
383-
const langSet = new Set();
384-
385-
await i18n.getAcceptLanguages().then(langs => {
386-
for ( const lang of langs.map(dropCountry) ) {
387-
langSet.add(lang);
388-
}
389-
});
390-
langSet.add(dropCountry(i18n.getUILanguage()));
391-
392-
const reTargetLang = new RegExp(
393-
`\\b(${Array.from(langSet).join('|')})\\b`
394-
);
395-
396-
const rulesetDetails = await getRulesetDetails();
397-
for ( const [ id, details ] of rulesetDetails ) {
398-
if ( typeof details.lang !== 'string' ) { continue; }
399-
if ( reTargetLang.test(details.lang) === false ) { continue; }
400-
out.push(id);
401-
}
402-
return out;
403-
}
404-
405-
/******************************************************************************/
406-
407116
async function hasGreatPowers(origin) {
408117
return browser.permissions.contains({
409118
origins: [ `${origin}/*` ]
@@ -491,7 +200,9 @@ function onMessage(request, sender, callback) {
491200

492201
case 'toggleTrustedSiteDirective': {
493202
toggleTrustedSiteDirective(request).then(response => {
494-
callback(response);
203+
registerInjectable().then(( ) => {
204+
callback(response);
205+
});
495206
});
496207
return true;
497208
}

0 commit comments

Comments
 (0)