[mv3] Merge Safari branch

Safari version of uBO Lite can now be built from master branch.

Related issue:
https://github.com/uBlockOrigin/uBOL-home/issues/327
This commit is contained in:
Raymond Hill 2025-04-19 13:08:59 -04:00
parent 8016e7733a
commit b5651417aa
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
17 changed files with 466 additions and 173 deletions

View file

@ -2,7 +2,7 @@
run_options := $(filter-out $@,$(MAKECMDGOALS))
.PHONY: all clean cleanassets test lint chromium opera firefox npm dig \
mv3-chromium mv3-firefox mv3-edge \
mv3-chromium mv3-firefox mv3-edge mv3-safari \
compare maxcost medcost mincost modifiers record wasm
sources := $(wildcard assets/* assets/*/* dist/version src/* src/*/* src/*/*/* src/*/*/*/*)
@ -80,6 +80,11 @@ dist/build/uBOLite.edge: tools/make-mv3.sh tools/make-edge.mjs $(sources) $(plat
mv3-edge: dist/build/uBOLite.edge
dist/build/uBOLite.safari: tools/make-mv3.sh $(sources) $(platform) $(mv3-data) dist/build/mv3-data
tools/make-mv3.sh safari
mv3-safari: dist/build/uBOLite.safari
dist/build/uAssets:
tools/pull-assets.sh

View file

@ -37,7 +37,7 @@ import {
} from './mode-manager.js';
import { broadcastMessage } from './utils.js';
import { dnr } from './ext.js';
import { dnr } from './ext-compat.js';
import { registerInjectables } from './scripting-manager.js';
import { rulesetConfig } from './config.js';
import { ubolLog } from './debug.js';

View file

@ -44,7 +44,6 @@ import {
import {
browser,
dnr,
localRead, localRemove, localWrite,
runtime,
windows,
@ -75,11 +74,12 @@ import {
saveRulesetConfig,
} from './config.js';
import { dnr } from './ext-compat.js';
import { registerInjectables } from './scripting-manager.js';
/******************************************************************************/
const UBOL_ORIGIN = runtime.getURL('').replace(/\/$/, '');
const UBOL_ORIGIN = runtime.getURL('').replace(/\/$/, '').toLowerCase();
const canShowBlockedCount = typeof dnr.setExtensionActionOptions === 'function';
@ -205,7 +205,9 @@ function onMessage(request, sender, callback) {
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/MessageSender
// Firefox API does not set `sender.origin`
if ( sender.origin !== undefined && sender.origin !== UBOL_ORIGIN ) { return; }
if ( sender.origin !== undefined ) {
if ( sender.origin.toLowerCase() !== UBOL_ORIGIN ) { return; }
}
switch ( request.what ) {

View file

@ -19,11 +19,19 @@
Home: https://github.com/gorhill/uBlock
*/
import { dnr } from './ext.js';
import { INITIATOR_DOMAINS, dnr } from './ext-compat.js';
import { browser } from './ext.js';
/******************************************************************************/
export const isSideloaded = dnr.onRuleMatchedDebug instanceof Object;
const isModern = dnr.onRuleMatchedDebug instanceof Object;
export const isSideloaded = (( ) => {
if ( isModern ) { return true; }
if ( typeof dnr.getMatchedRules === 'function' ) { return true; }
const manifest = browser.runtime.getManifest();
return manifest.permissions?.includes('declarativeNetRequestFeedback') ?? false;
})();
/******************************************************************************/
@ -67,8 +75,8 @@ const getRuleset = async rulesetId => {
if ( condition.requestDomains ) {
condition.requestDomains = pruneLongLists(condition.requestDomains);
}
if ( condition.initiatorDomains ) {
condition.initiatorDomains = pruneLongLists(condition.initiatorDomains);
if ( condition[INITIATOR_DOMAINS] ) {
condition[INITIATOR_DOMAINS] = pruneLongLists(condition[INITIATOR_DOMAINS]);
}
}
const ruleId = rule.id;
@ -92,20 +100,32 @@ export const getMatchedRules = (( ) => {
const noopFn = ( ) => Promise.resolve([]);
if ( isSideloaded !== true ) { return noopFn; }
return async tabId => {
const promises = [];
for ( let i = 0; i < bufferSize; i++ ) {
const j = (writePtr + i) % bufferSize;
const ruleInfo = matchedRules[j];
if ( ruleInfo === null ) { continue; }
if ( ruleInfo.request.tabId !== -1 ) {
if ( ruleInfo.request.tabId !== tabId ) { continue; }
if ( isModern ) {
return async tabId => {
const promises = [];
for ( let i = 0; i < bufferSize; i++ ) {
const j = (writePtr + i) % bufferSize;
const ruleInfo = matchedRules[j];
if ( ruleInfo === null ) { continue; }
if ( ruleInfo.request.tabId !== -1 ) {
if ( ruleInfo.request.tabId !== tabId ) { continue; }
}
const promise = getRuleDetails(ruleInfo);
if ( promise === undefined ) { continue; }
promises.unshift(promise);
}
const promise = getRuleDetails(ruleInfo);
if ( promise === undefined ) { continue; }
promises.unshift(promise);
return Promise.all(promises);
};
}
return async tabId => {
const matchedRules = await dnr.getMatchedRules({ tabId });
if ( matchedRules instanceof Object === false ) { return []; }
const out = [];
for ( const ruleInfo of matchedRules.rulesMatchedInfo ) {
out.push({ request: ruleInfo.request });
}
return Promise.all(promises);
return out;
};
})();
@ -118,6 +138,7 @@ const matchedRuleListener = ruleInfo => {
export const toggleDeveloperMode = state => {
if ( isSideloaded !== true ) { return; }
if ( isModern === false ) { return; }
if ( state ) {
dnr.onRuleMatchedDebug.addListener(matchedRuleListener);
} else {

View file

@ -0,0 +1,96 @@
/*******************************************************************************
uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
Copyright (C) 2022-present Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
export const webext = self.browser || self.chrome;
export const dnr = webext.declarativeNetRequest;
export const INITIATOR_DOMAINS = 'initiatorDomains';
export const EXCLUDED_INITIATOR_DOMAINS = 'excludedInitiatorDomains';
/******************************************************************************/
const ruleCompare = (a, b) => a.id - b.id;
const isSameRules = (a, b) => {
a.sort(ruleCompare);
b.sort(ruleCompare);
return JSON.stringify(a) === JSON.stringify(b);
};
/******************************************************************************/
dnr.setAllowAllRules = async function(id, allowed, notAllowed, reverse) {
const [
beforeDynamicRules,
beforeSessionRules,
] = await Promise.all([
dnr.getDynamicRules({ ruleIds: [ id+0 ] }),
dnr.getSessionRules({ ruleIds: [ id+1 ] }),
]);
const addDynamicRules = [];
const addSessionRules = [];
if ( reverse || allowed.length || notAllowed.length ) {
const rule0 = {
id: id+0,
action: { type: 'allowAllRequests' },
condition: {
resourceTypes: [ 'main_frame' ],
},
priority: 1000000,
};
if ( allowed.length ) {
rule0.condition.requestDomains = allowed.slice();
} else if ( notAllowed.length ) {
rule0.condition.excludedRequestDomains = notAllowed.slice();
}
addDynamicRules.push(rule0);
// https://github.com/uBlockOrigin/uBOL-home/issues/114
// https://github.com/uBlockOrigin/uBOL-home/issues/247
const rule1 = {
id: id+1,
action: { type: 'allow' },
condition: {
tabIds: [ webext.tabs.TAB_ID_NONE ],
},
priority: 1000000,
};
if ( allowed.length ) {
rule1.condition.initiatorDomains = allowed.slice();
} else if ( notAllowed.length ) {
rule1.condition.excludedInitiatorDomains = notAllowed.slice();
}
addSessionRules.push(rule1);
}
if ( isSameRules(addDynamicRules, beforeDynamicRules) ) { return false; }
return Promise.all([
dnr.updateDynamicRules({
addRules: addDynamicRules,
removeRuleIds: beforeDynamicRules.map(r => r.id),
}),
dnr.updateSessionRules({
addRules: addSessionRules,
removeRuleIds: beforeSessionRules.map(r => r.id),
}),
]).then(( ) =>
true
).catch(( ) =>
false
);
};

View file

@ -19,16 +19,13 @@
Home: https://github.com/gorhill/uBlock
*/
export const browser =
self.browser instanceof Object &&
self.browser instanceof Element === false
? self.browser
: self.chrome;
import { webext } from './ext-compat.js';
export const dnr = browser.declarativeNetRequest;
/******************************************************************************/
export const browser = webext;
export const i18n = browser.i18n;
export const runtime = browser.runtime;
export const TAB_ID_NONE = browser.tabs.TAB_ID_NONE;
export const windows = browser.windows;
/******************************************************************************/

View file

@ -347,6 +347,7 @@ async function init() {
}
tabURL.href = url.href || '';
} catch {
return false;
}
if ( url !== undefined ) {

View file

@ -19,8 +19,9 @@
Home: https://github.com/gorhill/uBlock
*/
import { dnr, runtime } from './ext.js';
import { dom, qs$ } from './dom.js';
import { dnr } from './ext-compat.js';
import { runtime } from './ext.js';
import { sendMessage } from './ext.js';
/******************************************************************************/

View file

@ -20,8 +20,6 @@
*/
import {
TAB_ID_NONE,
dnr,
i18n,
localRead, localRemove, localWrite,
runtime,
@ -33,6 +31,7 @@ import {
saveRulesetConfig,
} from './config.js';
import { dnr } from './ext-compat.js';
import { fetchJSON } from './fetch.js';
import { getAdminRulesets } from './admin.js';
import { hasBroadHostPermissions } from './utils.js';
@ -440,124 +439,30 @@ async function updateSessionRules() {
/******************************************************************************/
async function filteringModesToDNR(modes) {
const [
dynamicRules,
sessionRules,
] = await Promise.all([
dnr.getDynamicRules({ ruleIds: [ TRUSTED_DIRECTIVE_BASE_RULE_ID+0 ] }),
dnr.getSessionRules({ ruleIds: [ TRUSTED_DIRECTIVE_BASE_RULE_ID+1 ] }),
]);
const dynamicRule = dynamicRules?.length && dynamicRules[0] || undefined;
const beforeRequestDomainSet = new Set(dynamicRule?.condition.requestDomains);
const beforeExcludedRrequestDomainSet = new Set(dynamicRule?.condition.excludedRequestDomains);
if ( dynamicRule !== undefined && beforeRequestDomainSet.size === 0 ) {
beforeRequestDomainSet.add('all-urls');
} else {
beforeExcludedRrequestDomainSet.add('all-urls');
}
const noneHostnames = new Set([ ...modes.none ]);
const notNoneHostnames = new Set([ ...modes.basic, ...modes.optimal, ...modes.complete ]);
let afterRequestDomainSet = new Set();
let afterExcludedRequestDomainSet = new Set();
if ( noneHostnames.has('all-urls') ) {
afterRequestDomainSet = new Set([ 'all-urls' ]);
afterExcludedRequestDomainSet = notNoneHostnames;
const requestDomains = [];
const excludedRequestDomains = [];
const allowEverywhere = noneHostnames.has('all-urls');
if ( allowEverywhere ) {
excludedRequestDomains.push(...notNoneHostnames);
} else {
afterRequestDomainSet = noneHostnames;
afterExcludedRequestDomainSet = new Set();
requestDomains.push(...noneHostnames);
}
const removeDynamicRuleIds = [];
const removeSessionRuleIds = [];
if ( dynamicRule ) {
removeDynamicRuleIds.push(TRUSTED_DIRECTIVE_BASE_RULE_ID+0);
removeSessionRuleIds.push(TRUSTED_DIRECTIVE_BASE_RULE_ID+1);
}
const allowEverywhere = afterRequestDomainSet.delete('all-urls');
const addDynamicRules = [];
const addSessionRules = [];
if (
allowEverywhere ||
afterRequestDomainSet.size !== 0 ||
afterExcludedRequestDomainSet.size !== 0
) {
const rule0 = {
id: TRUSTED_DIRECTIVE_BASE_RULE_ID+0,
action: { type: 'allowAllRequests' },
condition: {
resourceTypes: [ 'main_frame' ],
},
priority: 100,
};
if ( afterRequestDomainSet.size !== 0 ) {
rule0.condition.requestDomains =
Array.from(afterRequestDomainSet).sort();
} else if ( afterExcludedRequestDomainSet.size !== 0 ) {
rule0.condition.excludedRequestDomains =
Array.from(afterExcludedRequestDomainSet).sort();
}
addDynamicRules.push(rule0);
// https://github.com/uBlockOrigin/uBOL-home/issues/114
// https://github.com/uBlockOrigin/uBOL-home/issues/247
const rule1 = {
id: TRUSTED_DIRECTIVE_BASE_RULE_ID+1,
action: { type: 'allow' },
condition: {
tabIds: [ TAB_ID_NONE ],
},
priority: 100,
};
if ( rule0.condition.requestDomains ) {
rule1.condition.initiatorDomains =
rule0.condition.requestDomains.slice();
} else if ( rule0.condition.excludedRequestDomains ) {
rule1.condition.excludedInitiatorDomains =
rule0.condition.excludedRequestDomains.slice();
}
addSessionRules.push(rule1);
}
const noneCount = noneHostnames.has('all-urls')
? -notNoneHostnames.size
const noneCount = allowEverywhere
? notNoneHostnames.size
: noneHostnames.size;
const promises = [];
if ( isDifferentAllowRules(addDynamicRules, dynamicRules) ) {
promises.push(dnr.updateDynamicRules({
addRules: addDynamicRules,
removeRuleIds: removeDynamicRuleIds,
}));
ubolLog(`Add "allowAllRequests" dynamic rule for ${noneCount} sites`);
}
if ( isDifferentAllowRules(addSessionRules, sessionRules) ) {
promises.push(dnr.updateSessionRules({
addRules: addSessionRules,
removeRuleIds: removeSessionRuleIds,
}));
ubolLog(`Add "allow" session rule for ${noneCount} sites`);
}
if ( promises.length === 0 ) { return; }
return Promise.all(promises);
return dnr.setAllowAllRules(
TRUSTED_DIRECTIVE_BASE_RULE_ID,
requestDomains.sort(),
excludedRequestDomains.sort(),
allowEverywhere
).then(modified => {
if ( modified === false ) { return; }
ubolLog(`${allowEverywhere ? 'Enabled' : 'Disabled'} DNR filtering for ${noneCount} sites`);
});
}
const isDifferentAllowRules = (a = [], b = []) => {
if ( a.length !== b.length ) { return true; }
const pp = [
'requestDomains',
'excludedRequestDomains',
'initiatorDomains',
'excludedInitiatorDomains',
];
for ( const p of pp ) {
const ac = a.length && a[0].condition[p] || [];
const bc = b.length && b[0].condition[p] || [];
if ( ac.join() !== bc.join() ) { return true; }
}
return false;
};
/******************************************************************************/
async function defaultRulesetsFromLanguage() {

View file

@ -68,7 +68,9 @@ const arrayEq = (a = [], b = [], sort = true) => {
const normalizeMatches = matches => {
if ( matches.length <= 1 ) { return; }
if ( matches.includes('<all_urls>') === false ) { return; }
if ( matches.includes('<all_urls>') === false ) {
if ( matches.includes('*://*/*') === false ) { return; }
}
matches.length = 0;
matches.push('<all_urls>');
};
@ -555,6 +557,9 @@ function registerScriptlet(context, scriptletDetails) {
/******************************************************************************/
// Issue: Safari appears to completely ignore excludeMatches
// https://github.com/radiolondra/ExcludeMatches-Test
async function registerInjectables() {
if ( browser.scripting === undefined ) { return false; }

View file

@ -28,11 +28,13 @@ const zapper = self.uBOLZapper = self.uBOLZapper || {};
if ( zapper.injected ) { return; }
zapper.injected = true;
const webext = typeof browser === 'object' ? browser : chrome;
/******************************************************************************/
const sendMessage = msg => {
try {
chrome.runtime.sendMessage(msg).catch(( ) => { });
webext.runtime.sendMessage(msg).catch(( ) => { });
} catch {
}
};
@ -365,11 +367,10 @@ const onFrameMessage = function(msg) {
// can remove the iframe.
const bootstrap = async ( ) => {
const dynamicURL = new URL(chrome.runtime.getURL('/zapper-ui.html'));
const dynamicURL = new URL(webext.runtime.getURL('/zapper-ui.html'));
return new Promise(resolve => {
const frame = document.createElement('iframe');
frame.setAttribute(zapperSecret, '');
document.documentElement.append(frame);
frame.onload = ( ) => {
frame.onload = null;
frame.setAttribute(`${zapperSecret}-loaded`, '');
@ -382,7 +383,7 @@ const bootstrap = async ( ) => {
quitZapper();
};
const realURL = new URL(dynamicURL);
realURL.hostname = chrome.i18n.getMessage('@@extension_id');
realURL.hostname = webext.i18n.getMessage('@@extension_id');
frame.contentWindow.postMessage(
{ what: 'zapperStart' },
realURL.origin,
@ -394,7 +395,13 @@ const bootstrap = async ( ) => {
zapperFramePort: port,
});
};
frame.contentWindow.location = dynamicURL.href;
if ( dynamicURL.protocol !== 'safari-web-extension:' ) {
document.documentElement.append(frame);
frame.contentWindow.location = dynamicURL.href;
} else {
frame.setAttribute('src', dynamicURL.href);
document.documentElement.append(frame);
}
});
};

View file

@ -116,7 +116,7 @@ const matchesFromHostnames = hostnames => {
const hostnamesFromMatches = origins => {
const out = [];
for ( const origin of origins ) {
if ( origin === '<all_urls>' ) {
if ( origin === '<all_urls>' || origin === '*://*/*' ) {
out.push('all-urls');
continue;
}
@ -141,7 +141,10 @@ const broadcastMessage = message => {
// most browsers treat host_permissions as optional."
async function hasBroadHostPermissions() {
return browser.permissions.contains({ origins: [ '<all_urls>' ] });
return browser.permissions.getAll().then(permissions =>
permissions.origins.includes('<all_urls>') ||
permissions.origins.includes('*://*/*')
).catch(( ) => false);
}
/******************************************************************************/

View file

@ -71,7 +71,7 @@ const env = [
'user_stylesheet',
];
if ( platform === 'edge' ) {
if ( platform === 'edge' || platform === 'safari' ) {
env.push('chromium');
}
@ -318,6 +318,38 @@ const isURLSkip = rule =>
/******************************************************************************/
function patchRuleset(ruleset) {
if ( platform !== 'safari' ) { return ruleset; }
const out = [];
for ( const rule of ruleset ) {
const condition = rule.condition;
if ( rule.action.type === 'modifyHeaders' ) {
log(`Safari's incomplete API: ${JSON.stringify(rule)}`, true);
continue;
}
if ( Array.isArray(condition.requestMethods) ) {
log(`Safari's incomplete API: ${JSON.stringify(rule)}`, true);
continue;
}
if ( Array.isArray(condition.excludedRequestMethods) ) {
log(`Safari's incomplete API: ${JSON.stringify(rule)}`, true);
continue;
}
if ( Array.isArray(condition.initiatorDomains) ) {
condition.domains = condition.initiatorDomains;
delete condition.initiatorDomains;
}
if ( Array.isArray(condition.excludedInitiatorDomains) ) {
condition.excludedDomains = condition.excludedInitiatorDomains;
delete condition.excludedInitiatorDomains;
}
out.push(rule);
}
return out;
}
/******************************************************************************/
// Two distinct hostnames:
// www.example.com
// example.com
@ -489,7 +521,9 @@ async function processNetworkFilters(assetDetails, network) {
}
}
const plainGood = rules.filter(rule => isSafe(rule) && isRegex(rule) === false);
const plainGood = patchRuleset(
rules.filter(rule => isSafe(rule) && isRegex(rule) === false)
);
log(`\tPlain good: ${plainGood.length}`);
log(plainGood
.filter(rule => Array.isArray(rule._warning))
@ -497,12 +531,16 @@ async function processNetworkFilters(assetDetails, network) {
.join('\n'), true
);
const regexes = rules.filter(rule => isSafe(rule) && isRegex(rule));
const regexes = patchRuleset(
rules.filter(rule => isSafe(rule) && isRegex(rule))
);
log(`\tMaybe good (regexes): ${regexes.length}`);
const redirects = rules.filter(rule =>
isUnsupported(rule) === false &&
isRedirect(rule)
const redirects = patchRuleset(
rules.filter(rule =>
isUnsupported(rule) === false &&
isRedirect(rule)
)
);
redirects.forEach(rule => {
if ( rule.action.redirect.extensionPath === undefined ) { return; }
@ -512,17 +550,23 @@ async function processNetworkFilters(assetDetails, network) {
});
log(`\tredirect=: ${redirects.length}`);
const removeparamsGood = rules.filter(rule =>
isUnsupported(rule) === false && isRemoveparam(rule)
const removeparamsGood = patchRuleset(
rules.filter(rule =>
isUnsupported(rule) === false && isRemoveparam(rule)
)
);
const removeparamsBad = rules.filter(rule =>
isUnsupported(rule) && isRemoveparam(rule)
const removeparamsBad = patchRuleset(
rules.filter(rule =>
isUnsupported(rule) && isRemoveparam(rule)
)
);
log(`\tremoveparams= (accepted/discarded): ${removeparamsGood.length}/${removeparamsBad.length}`);
const modifyHeaders = rules.filter(rule =>
isUnsupported(rule) === false &&
isModifyHeaders(rule)
const modifyHeaders = patchRuleset(
rules.filter(rule =>
isUnsupported(rule) === false &&
isModifyHeaders(rule)
)
);
log(`\tmodifyHeaders=: ${modifyHeaders.length}`);
@ -1401,10 +1445,10 @@ async function main() {
// Patch web_accessible_resources key
manifest.web_accessible_resources = manifest.web_accessible_resources || [];
const web_accessible_resources = {
resources: Array.from(requiredRedirectResources).map(path => `/${path}`),
resources: Array.from(requiredRedirectResources).map(path => `${path}`),
matches: [ '<all_urls>' ],
};
if ( env.includes('chromium') ) {
if ( env.includes('chromium') && env.includes('safari') === false ) {
web_accessible_resources.use_dynamic_url = true;
}
manifest.web_accessible_resources.push(web_accessible_resources);

View file

@ -0,0 +1,131 @@
/*******************************************************************************
uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
Copyright (C) 2022-present Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
export const webext = self.browser;
export const INITIATOR_DOMAINS = 'domains';
export const EXCLUDED_INITIATOR_DOMAINS = 'excludedDomains';
/******************************************************************************/
// https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/declarativeNetRequest/
const nativeDNR = webext.declarativeNetRequest;
const isSupportedRule = r => {
if ( r.action?.responseHeaders ) { return false; }
if ( r.condition?.tabIds !== undefined ) { return false; }
return true;
};
const ruleCompare = (a, b) => a.id - b.id;
const isSameRules = (a, b) => {
a.sort(ruleCompare);
b.sort(ruleCompare);
return JSON.stringify(a) === JSON.stringify(b);
};
/******************************************************************************/
export const dnr = {
DYNAMIC_RULESET_ID: '_dynamic',
MAX_NUMBER_OF_ENABLED_STATIC_RULESETS: nativeDNR.MAX_NUMBER_OF_ENABLED_STATIC_RULESETS,
MAX_NUMBER_OF_REGEX_RULES: nativeDNR.MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES,
async getAvailableStaticRuleCount() {
return 150000;
},
getDynamicRules({ ruleIds } = {}) {
return new Promise(resolve => {
nativeDNR.getDynamicRules(rules => {
if ( Array.isArray(rules) === false ) { return resolve([]); }
if ( Array.isArray(ruleIds) === false ) { return resolve(rules); }
return resolve(rules.filter(rule => ruleIds.includes(rule.id)));
});
});
},
getEnabledRulesets(...args) {
return nativeDNR.getEnabledRulesets(...args);
},
getMatchedRules(...args) {
return nativeDNR.getMatchedRules(...args);
},
getSessionRules({ ruleIds } = {}) {
return new Promise(resolve => {
nativeDNR.getSessionRules(rules => {
if ( Array.isArray(rules) === false ) { return resolve([]); }
if ( Array.isArray(ruleIds) === false ) { return resolve(rules); }
return resolve(rules.filter(rule => ruleIds.includes(rule.id)));
});
});
},
isRegexSupported(...args) {
return nativeDNR.isRegexSupported(...args);
},
async updateDynamicRules(optionsBefore) {
const { addRules, removeRuleIds } = optionsBefore;
const addRulesAfter = addRules?.filter(isSupportedRule);
if ( Boolean(addRulesAfter?.length || removeRuleIds?.length) === false ) { return; }
const optionsAfter = {};
if ( addRulesAfter?.length ) { optionsAfter.addRules = addRulesAfter; }
if ( removeRuleIds?.length ) { optionsAfter.removeRuleIds = removeRuleIds; }
return nativeDNR.updateDynamicRules(optionsAfter);
},
updateEnabledRulesets(...args) {
return nativeDNR.updateEnabledRulesets(...args);
},
async updateSessionRules(optionsBefore) {
const { addRules, removeRuleIds } = optionsBefore;
const addRulesAfter = addRules?.filter(isSupportedRule);
if ( Boolean(addRulesAfter?.length || removeRuleIds?.length) === false ) { return; }
const optionsAfter = {};
if ( optionsAfter?.length ) { optionsAfter.addRules = addRulesAfter; }
if ( removeRuleIds?.length ) { optionsAfter.removeRuleIds = removeRuleIds; }
return nativeDNR.updateSessionRules(optionsAfter);
},
async setAllowAllRules(id, allowed, notAllowed, reverse) {
const beforeRules = await this.getDynamicRules({ ruleIds: [ id+0 ] });
const addRules = [];
if ( reverse || allowed.length || notAllowed.length ) {
const rule0 = {
id: id+0,
action: { type: 'allow' },
condition: { urlFilter: '*' },
priority: 1000000,
};
if ( allowed.length ) {
rule0.condition.domains = allowed;
} else if ( notAllowed.length ) {
rule0.condition.excludedDomains = notAllowed;
}
addRules.push(rule0);
}
if ( isSameRules(addRules, beforeRules) ) { return false; }
return this.updateDynamicRules({
addRules,
removeRuleIds: beforeRules.map(r => r.id),
}).then(( ) =>
true
).catch(( ) =>
false
);
},
};

View file

@ -0,0 +1,64 @@
{
"action": {
"default_icon": "/img/icon_64.png",
"default_popup": "popup.html"
},
"author": "Raymond Hill",
"background": {
"scripts": [ "/js/background.js" ],
"type": "module"
},
"commands": {
"enter-zapper-mode": {
"description": "__MSG_zapperTipEnter__"
}
},
"declarative_net_request": {
"rule_resources": [
]
},
"default_locale": "en",
"description": "__MSG_extShortDesc__",
"icons": {
"16": "/img/icon_16.png",
"32": "/img/icon_32.png",
"64": "/img/icon_64.png",
"128": "/img/icon_128.png",
"512": "/img/icon_512.png"
},
"manifest_version": 3,
"name": "__MSG_extName__",
"options_ui": {
"page": "dashboard.html"
},
"host_permissions": [
"<all_urls>"
],
"permissions": [
"activeTab",
"declarativeNetRequest",
"declarativeNetRequestWithHostAccess",
"scripting",
"storage"
],
"short_name": "uBO Lite",
"version": "1.0",
"web_accessible_resources": [
{
"resources": [
"strictblock.html"
],
"matches": [
"<all_urls>"
]
},
{
"resources": [
"zapper-ui.html"
],
"matches": [
"<all_urls>"
]
}
]
}

View file

@ -437,6 +437,20 @@ function finalizeRuleset(context, network) {
mergeRules(rulesetMap, 'requestDomains');
mergeRules(rulesetMap, 'responseHeaders');
// Convert back single-entry requestDomains into pattern-based filters
// https://github.com/uBlockOrigin/uBOL-home/issues/327
// TODO: Remove when (if) Safari is changed to interpret requestDomains as
// in other browsers.
for ( const rule of rulesetMap.values() ) {
const { condition } = rule;
if ( condition?.requestDomains === undefined ) { continue; }
if ( condition.requestDomains.length !== 1 ) { continue; }
if ( condition.urlFilter !== undefined ) { continue; }
if ( condition.regexFilter !== undefined ) { continue; }
condition.urlFilter = `||${condition.requestDomains[0]}^`;
condition.requestDomains = undefined;
}
// Patch id
const rulesetFinal = [];
{

View file

@ -23,6 +23,9 @@ for i in "$@"; do
edge)
PLATFORM="edge"
;;
safari)
PLATFORM="safari"
;;
uBOLite_+([0-9]).+([0-9]).+([0-9]).+([0-9]))
TAGNAME="$i"
FULL="yes"
@ -82,13 +85,12 @@ cp -R "$UBO_DIR/src/img/flags-of-the-world" "$DES"/img
cp LICENSE.txt "$DES"/
echo "*** uBOLite.mv3: Copying mv3-specific files"
if [ "$PLATFORM" = "firefox" ]; then
cp platform/mv3/firefox/background.html "$DES"/
fi
cp platform/mv3/"$PLATFORM"/manifest.json "$DES"/
cp platform/mv3/extension/*.html "$DES"/
cp platform/mv3/extension/*.json "$DES"/
cp platform/mv3/extension/css/* "$DES"/css/
cp -R platform/mv3/extension/js/* "$DES"/js/
cp platform/mv3/"$PLATFORM"/ext-compat.js "$DES"/js/ 2>/dev/null || :
cp platform/mv3/extension/img/* "$DES"/img/
cp -R platform/mv3/extension/_locales "$DES"/
cp platform/mv3/README.md "$DES/"
@ -96,11 +98,6 @@ cp platform/mv3/README.md "$DES/"
echo "*** uBOLite.mv3: Generating rulesets"
TMPDIR=$(mktemp -d)
mkdir -p "$TMPDIR"
if [ "$PLATFORM" = "chromium" ] || [ "$PLATFORM" = "edge" ]; then
cp platform/mv3/chromium/manifest.json "$DES"/
elif [ "$PLATFORM" = "firefox" ]; then
cp platform/mv3/firefox/manifest.json "$DES"/
fi
./tools/make-nodejs.sh "$TMPDIR"
cp platform/mv3/*.json "$TMPDIR"/
cp platform/mv3/*.js "$TMPDIR"/