mirror of
https://github.com/gorhill/uBlock.git
synced 2025-05-01 21:23:18 +00:00
[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:
parent
8016e7733a
commit
b5651417aa
17 changed files with 466 additions and 173 deletions
7
Makefile
7
Makefile
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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 ) {
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
96
platform/mv3/extension/js/ext-compat.js
Normal file
96
platform/mv3/extension/js/ext-compat.js
Normal 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
|
||||
);
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
|||
|
|
@ -347,6 +347,7 @@ async function init() {
|
|||
}
|
||||
tabURL.href = url.href || '';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( url !== undefined ) {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
131
platform/mv3/safari/ext-compat.js
Normal file
131
platform/mv3/safari/ext-compat.js
Normal 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
|
||||
);
|
||||
},
|
||||
};
|
||||
64
platform/mv3/safari/manifest.json
Normal file
64
platform/mv3/safari/manifest.json
Normal 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>"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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 = [];
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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"/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue