[mv3] Fix equality test between two rulesets

Related issue:
https://github.com/uBlockOrigin/uBOL-home/issues/591
This commit is contained in:
Raymond Hill 2026-01-17 13:19:51 -05:00
parent 61f30ea1fb
commit 222f4fbbc1
No known key found for this signature in database
GPG key ID: F5630CAE62A14316
7 changed files with 133 additions and 90 deletions

View file

@ -55,8 +55,6 @@ import {
import {
broadcastMessage,
gotoURL,
hasBroadHostPermissions,
hostnameFromMatch,
hostnamesFromMatches,
} from './utils.js';
@ -101,6 +99,11 @@ import {
ubolLog,
} from './debug.js';
import {
gotoURL,
hasBroadHostPermissions,
} from './ext-utils.js';
import { dnr } from './ext-compat.js';
import { toggleToolbarIcon } from './action.js';
@ -662,12 +665,14 @@ async function startSession() {
}
// Permissions may have been removed while the extension was disabled
await syncWithBrowserPermissions();
const permissionsUpdated = await syncWithBrowserPermissions();
// Unsure whether the browser remembers correctly registered css/scripts
// after we quit the browser. For now uBOL will check unconditionally at
// launch time whether content css/scripts are properly registered.
registerInjectables();
if ( isNewVersion || permissionsUpdated ) {
registerInjectables();
}
// Cosmetic filtering-related content scripts cache fitlering data in
// session storage.

View file

@ -19,21 +19,13 @@
Home: https://github.com/gorhill/uBlock
*/
import { deepEquals } from './utils.js';
export const webext = self.browser || self.chrome;
export const dnr = webext.declarativeNetRequest || {};
/******************************************************************************/
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 function normalizeDNRRules(rules, ruleIds) {
if ( Array.isArray(rules) === false ) { return rules; }
return Array.isArray(ruleIds)
@ -85,19 +77,23 @@ dnr.setAllowAllRules = async function(id, allowed, notAllowed, reverse, priority
}
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
);
const promises = [];
const modified = deepEquals(addDynamicRules, beforeDynamicRules) === false;
if ( modified ) {
promises.push(
dnr.updateDynamicRules({
addRules: addDynamicRules,
removeRuleIds: beforeDynamicRules.map(r => r.id),
})
);
}
if ( deepEquals(addSessionRules, beforeSessionRules) === false ) {
promises.push(
dnr.updateSessionRules({
addRules: addSessionRules,
removeRuleIds: beforeSessionRules.map(r => r.id),
})
);
}
return Promise.all(promises).then(( ) => modified).catch(( ) => false);
};

View file

@ -0,0 +1,68 @@
/*******************************************************************************
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
*/
import {
browser,
runtime,
} from './ext.js';
/******************************************************************************/
// https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/manifest.json/host_permissions#requested_permissions_and_user_prompts
// "Users can grant or revoke host permissions on an ad hoc basis. Therefore,
// most browsers treat host_permissions as optional."
export async function hasBroadHostPermissions() {
return browser.permissions.getAll().then(permissions =>
permissions.origins.includes('<all_urls>') ||
permissions.origins.includes('*://*/*')
).catch(( ) => false);
}
/******************************************************************************/
export async function gotoURL(url, type) {
const pageURL = new URL(url, runtime.getURL('/'));
const tabs = await browser.tabs.query({
url: pageURL.href,
windowType: type !== 'popup' ? 'normal' : 'popup'
});
if ( Array.isArray(tabs) && tabs.length !== 0 ) {
const { windowId, id } = tabs[0];
return Promise.all([
browser.windows.update(windowId, { focused: true }),
browser.tabs.update(id, { active: true }),
]);
}
if ( type === 'popup' ) {
return browser.windows.create({
type: 'popup',
url: pageURL.href,
});
}
return browser.tabs.create({
active: true,
url: pageURL.href,
});
}

View file

@ -21,7 +21,6 @@
import {
broadcastMessage,
hasBroadHostPermissions,
hostnamesFromMatches,
isDescendantHostnameOfIter,
toBroaderHostname,
@ -40,6 +39,8 @@ import {
import { adminReadEx } from './admin.js';
import { filteringModesToDNR } from './ruleset-manager.js';
import { hasBroadHostPermissions } from './ext-utils.js';
/******************************************************************************/

View file

@ -36,7 +36,7 @@ import { ubolErr, ubolLog } from './debug.js';
import { dnr } from './ext-compat.js';
import { fetchJSON } from './fetch.js';
import { getAdminRulesets } from './admin.js';
import { hasBroadHostPermissions } from './utils.js';
import { hasBroadHostPermissions } from './ext-utils.js';
import { rulesFromText } from './dnr-parser.js';
/******************************************************************************/

View file

@ -19,11 +19,6 @@
Home: https://github.com/gorhill/uBlock
*/
import {
browser,
runtime,
} from './ext.js';
/******************************************************************************/
function parsedURLromOrigin(origin) {
@ -135,53 +130,40 @@ export const hostnamesFromMatches = origins => {
/******************************************************************************/
const broadcastMessage = message => {
const bc = new self.BroadcastChannel('uBOL');
bc.postMessage(message);
export const deepEquals = (a, b) => {
switch ( typeof a ) {
case 'undefined':
case 'boolean':
case 'number':
case 'string':
return a === b;
}
// case 'object':
if ( typeof b !== 'object' ) { return false; }
if ( a === null || b === null ) { return a === b; }
if ( Array.isArray(a) || Array.isArray(b) ) {
if ( Array.isArray(a) === false || Array.isArray(b) === false ) { return false; }
if ( a.length !== b.length ) { return false; }
for ( let i = 0; i < a.length; i++ ) {
if ( deepEquals(a[i], b[i]) === false ) { return false; }
}
return true;
}
const akeys = Object.keys(a);
const bkeys = Object.keys(b);
if ( akeys.length !== bkeys.length ) { return false; }
for ( const k of akeys ) {
if ( deepEquals(a[k], b[k]) === false ) { return false; }
}
return true;
};
/******************************************************************************/
// https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/manifest.json/host_permissions#requested_permissions_and_user_prompts
// "Users can grant or revoke host permissions on an ad hoc basis. Therefore,
// most browsers treat host_permissions as optional."
async function hasBroadHostPermissions() {
return browser.permissions.getAll().then(permissions =>
permissions.origins.includes('<all_urls>') ||
permissions.origins.includes('*://*/*')
).catch(( ) => false);
}
/******************************************************************************/
async function gotoURL(url, type) {
const pageURL = new URL(url, runtime.getURL('/'));
const tabs = await browser.tabs.query({
url: pageURL.href,
windowType: type !== 'popup' ? 'normal' : 'popup'
});
if ( Array.isArray(tabs) && tabs.length !== 0 ) {
const { windowId, id } = tabs[0];
return Promise.all([
browser.windows.update(windowId, { focused: true }),
browser.tabs.update(id, { active: true }),
]);
}
if ( type === 'popup' ) {
return browser.windows.create({
type: 'popup',
url: pageURL.href,
});
}
return browser.tabs.create({
active: true,
url: pageURL.href,
});
}
const broadcastMessage = message => {
const bc = new self.BroadcastChannel('uBOL');
bc.postMessage(message);
};
/******************************************************************************/
@ -220,7 +202,5 @@ export {
isDescendantHostnameOfIter,
intersectHostnameIters,
subtractHostnameIters,
hasBroadHostPermissions,
gotoURL,
strArrayEq,
};

View file

@ -19,6 +19,7 @@
Home: https://github.com/gorhill/uBlock
*/
import { deepEquals } from './utils.js';
export const webext = self.browser;
@ -114,14 +115,6 @@ const prepareUpdateRules = optionsBefore => {
return optionsAfter;
};
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 function normalizeDNRRules(rules, ruleIds) {
@ -209,7 +202,7 @@ export const dnr = {
}
addRules.push(rule0);
}
if ( isSameRules(addRules, beforeRules) ) { return false; }
if ( deepEquals(addRules, beforeRules) ) { return false; }
return this.updateDynamicRules({
addRules,
removeRuleIds: beforeRules.map(r => r.id),