mirror of
https://github.com/gorhill/uBlock.git
synced 2026-03-11 09:04:36 +00:00
[mv3] Try to address Safari's webextensions API issues
1) Reset "seen realms" when modifying enabled rulesets. The "seen realms" code path is an attempt at mitigating an issue whereas the DNR rules would not be enforced under certain circumstances. 2) Re-write the scripting manager to always wholly unregister/register all content scripts instead of only the differences before/after following a change affecting content scripts.
This commit is contained in:
parent
d3024ac309
commit
0101a47d25
4 changed files with 63 additions and 195 deletions
|
|
@ -19,8 +19,8 @@
|
|||
Home: https://github.com/gorhill/uBlock
|
||||
*/
|
||||
|
||||
import { matchesFromHostnames, strArrayEq } from './utils.js';
|
||||
import { browser } from './ext.js';
|
||||
import { matchesFromHostnames } from './utils.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
@ -91,9 +91,6 @@ export async function registerToolbarIconToggler(context) {
|
|||
|
||||
if ( toToggle.size === 0 ) { return; }
|
||||
|
||||
const registered = context.before.get('toolbar-icon');
|
||||
context.before.delete('toolbar-icon'); // Important!
|
||||
|
||||
const directive = {
|
||||
id: 'toolbar-icon',
|
||||
js: [ '/js/scripting/toolbar-icon.js' ],
|
||||
|
|
@ -101,10 +98,5 @@ export async function registerToolbarIconToggler(context) {
|
|||
runAt: 'document_start',
|
||||
};
|
||||
|
||||
if ( registered === undefined ) {
|
||||
context.toAdd.push(directive);
|
||||
} else if ( strArrayEq(registered.matches, directive.matches) === false ) {
|
||||
context.toRemove.push('toolbar-icon');
|
||||
context.toAdd.push(directive);
|
||||
}
|
||||
context.toAdd.push(directive);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import {
|
|||
import {
|
||||
intersectHostnameIters,
|
||||
matchesFromHostnames,
|
||||
strArrayEq,
|
||||
subtractHostnameIters,
|
||||
} from './utils.js';
|
||||
|
||||
|
|
@ -189,9 +188,6 @@ export async function registerCustomFilters(context) {
|
|||
}
|
||||
if ( hostnames.length === 0 ) { return; }
|
||||
|
||||
const registered = context.before.get('css-user');
|
||||
context.before.delete('css-user'); // Important!
|
||||
|
||||
const directive = {
|
||||
id: 'css-user',
|
||||
js: [ '/js/scripting/css-user.js' ],
|
||||
|
|
@ -199,12 +195,7 @@ export async function registerCustomFilters(context) {
|
|||
runAt: 'document_start',
|
||||
};
|
||||
|
||||
if ( registered === undefined ) {
|
||||
context.toAdd.push(directive);
|
||||
} else if ( strArrayEq(registered.matches, directive.matches) === false ) {
|
||||
context.toRemove.push('css-user');
|
||||
context.toAdd.push(directive);
|
||||
}
|
||||
context.toAdd.push(directive);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
|||
|
|
@ -71,29 +71,6 @@ const normalizeMatches = matches => {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
// The extensions API does not always return exactly what we fed it, so we
|
||||
// need to normalize some entries to be sure we properly detect changes when
|
||||
// comparing registered entries vs. entries to register.
|
||||
|
||||
const normalizeRegisteredContentScripts = registered => {
|
||||
for ( const entry of registered ) {
|
||||
const { css = [], js = [] } = entry;
|
||||
for ( let i = 0; i < css.length; i++ ) {
|
||||
const path = css[i];
|
||||
if ( path.startsWith('/') ) { continue; }
|
||||
css[i] = `/${path}`;
|
||||
}
|
||||
for ( let i = 0; i < js.length; i++ ) {
|
||||
const path = js[i];
|
||||
if ( path.startsWith('/') ) { continue; }
|
||||
js[i] = `/${path}`;
|
||||
}
|
||||
}
|
||||
return registered;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function resetCSSCache() {
|
||||
const keys = await sessionKeys();
|
||||
return Promise.all(
|
||||
|
|
@ -104,7 +81,7 @@ async function resetCSSCache() {
|
|||
/******************************************************************************/
|
||||
|
||||
function registerHighGeneric(context, genericDetails) {
|
||||
const { before, filteringModeDetails, rulesetsDetails } = context;
|
||||
const { filteringModeDetails, rulesetsDetails } = context;
|
||||
|
||||
const excludeHostnames = [];
|
||||
const includeHostnames = [];
|
||||
|
|
@ -145,12 +122,8 @@ function registerHighGeneric(context, genericDetails) {
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( matches.length === 0 ) { return; }
|
||||
|
||||
const registered = before.get('css-generichigh');
|
||||
before.delete('css-generichigh'); // Important!
|
||||
|
||||
// https://github.com/w3c/webextensions/issues/414#issuecomment-1623992885
|
||||
// Once supported, add:
|
||||
// cssOrigin: 'USER',
|
||||
|
|
@ -166,26 +139,13 @@ function registerHighGeneric(context, genericDetails) {
|
|||
}
|
||||
|
||||
// register
|
||||
if ( registered === undefined ) {
|
||||
context.toAdd.push(directive);
|
||||
return;
|
||||
}
|
||||
|
||||
// update
|
||||
if (
|
||||
ut.strArrayEq(registered.css, css, false) === false ||
|
||||
ut.strArrayEq(registered.matches, matches) === false ||
|
||||
ut.strArrayEq(registered.excludeMatches, excludeMatches) === false
|
||||
) {
|
||||
context.toRemove.push('css-generichigh');
|
||||
context.toAdd.push(directive);
|
||||
}
|
||||
context.toAdd.push(directive);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function registerGeneric(context, genericDetails) {
|
||||
const { before, filteringModeDetails, rulesetsDetails } = context;
|
||||
const { filteringModeDetails, rulesetsDetails } = context;
|
||||
|
||||
const excludedByFilter = [];
|
||||
const includedByFilter = [];
|
||||
|
|
@ -224,8 +184,6 @@ function registerGeneric(context, genericDetails) {
|
|||
),
|
||||
];
|
||||
if ( matches.length === 0 ) { return; }
|
||||
const registered = before.get('css-generic-some');
|
||||
before.delete('css-generic-some'); // Important!
|
||||
const directive = {
|
||||
id: 'css-generic-some',
|
||||
js,
|
||||
|
|
@ -233,15 +191,7 @@ function registerGeneric(context, genericDetails) {
|
|||
matches,
|
||||
runAt: 'document_idle',
|
||||
};
|
||||
if ( registered === undefined ) { // register
|
||||
context.toAdd.push(directive);
|
||||
} else if ( // update
|
||||
ut.strArrayEq(registered.js, js, false) === false ||
|
||||
ut.strArrayEq(registered.matches, directive.matches) === false
|
||||
) {
|
||||
context.toRemove.push('css-generic-some');
|
||||
context.toAdd.push(directive);
|
||||
}
|
||||
context.toAdd.push(directive);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -249,8 +199,6 @@ function registerGeneric(context, genericDetails) {
|
|||
...ut.matchesFromHostnames(excludedByMode),
|
||||
...ut.matchesFromHostnames(excludedByFilter),
|
||||
];
|
||||
const registeredAll = before.get('css-generic-all');
|
||||
before.delete('css-generic-all'); // Important!
|
||||
const directiveAll = {
|
||||
id: 'css-generic-all',
|
||||
js,
|
||||
|
|
@ -261,24 +209,14 @@ function registerGeneric(context, genericDetails) {
|
|||
if ( excludeMatches.length !== 0 ) {
|
||||
directiveAll.excludeMatches = excludeMatches;
|
||||
}
|
||||
context.toAdd.push(directiveAll);
|
||||
|
||||
if ( registeredAll === undefined ) { // register
|
||||
context.toAdd.push(directiveAll);
|
||||
} else if ( // update
|
||||
ut.strArrayEq(registeredAll.js, js, false) === false ||
|
||||
ut.strArrayEq(registeredAll.excludeMatches, directiveAll.excludeMatches) === false
|
||||
) {
|
||||
context.toRemove.push('css-generic-all');
|
||||
context.toAdd.push(directiveAll);
|
||||
}
|
||||
const matches = [
|
||||
...ut.matchesFromHostnames(
|
||||
ut.subtractHostnameIters(includedByFilter, excludedByMode)
|
||||
),
|
||||
];
|
||||
if ( matches.length === 0 ) { return; }
|
||||
const registeredSome = before.get('css-generic-some');
|
||||
before.delete('css-generic-some'); // Important!
|
||||
const directiveSome = {
|
||||
id: 'css-generic-some',
|
||||
js,
|
||||
|
|
@ -286,21 +224,13 @@ function registerGeneric(context, genericDetails) {
|
|||
matches,
|
||||
runAt: 'document_idle',
|
||||
};
|
||||
if ( registeredSome === undefined ) { // register
|
||||
context.toAdd.push(directiveSome);
|
||||
} else if ( // update
|
||||
ut.strArrayEq(registeredSome.js, js, false) === false ||
|
||||
ut.strArrayEq(registeredSome.matches, directiveSome.matches) === false
|
||||
) {
|
||||
context.toRemove.push('css-generic-some');
|
||||
context.toAdd.push(directiveSome);
|
||||
}
|
||||
context.toAdd.push(directiveSome);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function registerCosmetic(realm, context) {
|
||||
const { before, filteringModeDetails, rulesetsDetails } = context;
|
||||
const { filteringModeDetails, rulesetsDetails } = context;
|
||||
|
||||
{
|
||||
const keys = await localKeys();
|
||||
|
|
@ -355,9 +285,6 @@ async function registerCosmetic(realm, context) {
|
|||
}
|
||||
}
|
||||
|
||||
const registered = before.get(realmid);
|
||||
before.delete(realmid); // Important!
|
||||
|
||||
const directive = {
|
||||
id: realmid,
|
||||
js,
|
||||
|
|
@ -370,26 +297,13 @@ async function registerCosmetic(realm, context) {
|
|||
}
|
||||
|
||||
// register
|
||||
if ( registered === undefined ) {
|
||||
context.toAdd.push(directive);
|
||||
return;
|
||||
}
|
||||
|
||||
// update
|
||||
if (
|
||||
ut.strArrayEq(registered.js, js, false) === false ||
|
||||
ut.strArrayEq(registered.matches, matches) === false ||
|
||||
ut.strArrayEq(registered.excludeMatches, excludeMatches) === false
|
||||
) {
|
||||
context.toRemove.push(realmid);
|
||||
context.toAdd.push(directive);
|
||||
}
|
||||
context.toAdd.push(directive);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function registerScriptlet(context, scriptletDetails) {
|
||||
const { before, filteringModeDetails, rulesetsDetails } = context;
|
||||
const { filteringModeDetails, rulesetsDetails } = context;
|
||||
|
||||
const hasBroadHostPermission =
|
||||
filteringModeDetails.optimal.has('all-urls') ||
|
||||
|
|
@ -431,9 +345,6 @@ function registerScriptlet(context, scriptletDetails) {
|
|||
matches.push(...ut.matchesFromHostnames(targetHostnames));
|
||||
normalizeMatches(matches);
|
||||
|
||||
const registered = before.get(id);
|
||||
before.delete(id); // Important!
|
||||
|
||||
const directive = {
|
||||
id,
|
||||
js: [ `/rulesets/scripting/scriptlet/${world.toLowerCase()}/${rulesetId}.js` ],
|
||||
|
|
@ -448,19 +359,7 @@ function registerScriptlet(context, scriptletDetails) {
|
|||
}
|
||||
|
||||
// register
|
||||
if ( registered === undefined ) {
|
||||
context.toAdd.push(directive);
|
||||
continue;
|
||||
}
|
||||
|
||||
// update
|
||||
if (
|
||||
ut.strArrayEq(registered.matches, matches) === false ||
|
||||
ut.strArrayEq(registered.excludeMatches, excludeMatches) === false
|
||||
) {
|
||||
context.toRemove.push(id);
|
||||
context.toAdd.push(directive);
|
||||
}
|
||||
context.toAdd.push(directive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -481,26 +380,17 @@ export async function registerInjectables() {
|
|||
rulesetsDetails,
|
||||
scriptletDetails,
|
||||
genericDetails,
|
||||
registered,
|
||||
] = await Promise.all([
|
||||
getFilteringModeDetails(),
|
||||
getEnabledRulesetsDetails(),
|
||||
getScriptletDetails(),
|
||||
getGenericDetails(),
|
||||
browser.scripting.getRegisteredContentScripts(),
|
||||
]);
|
||||
const before = new Map(
|
||||
normalizeRegisteredContentScripts(registered).map(
|
||||
entry => [ entry.id, entry ]
|
||||
)
|
||||
);
|
||||
const toAdd = [], toRemove = [];
|
||||
const toAdd = [];
|
||||
const context = {
|
||||
filteringModeDetails,
|
||||
rulesetsDetails,
|
||||
before,
|
||||
toAdd,
|
||||
toRemove,
|
||||
};
|
||||
|
||||
await Promise.all([
|
||||
|
|
@ -513,23 +403,17 @@ export async function registerInjectables() {
|
|||
registerToolbarIconToggler(context),
|
||||
]);
|
||||
|
||||
toRemove.push(...Array.from(before.keys()));
|
||||
|
||||
if ( toRemove.length !== 0 ) {
|
||||
ubolLog(`Unregistered ${toRemove} content (css/js)`);
|
||||
try {
|
||||
await browser.scripting.unregisterContentScripts({ ids: toRemove });
|
||||
localRemove('$scripting.unregisterContentScripts');
|
||||
} catch(reason) {
|
||||
ubolErr(`unregisterContentScripts/${reason}`);
|
||||
}
|
||||
ubolLog(`Unregistered all content (css/js)`);
|
||||
try {
|
||||
await browser.scripting.unregisterContentScripts();
|
||||
} catch(reason) {
|
||||
ubolErr(`unregisterContentScripts/${reason}`);
|
||||
}
|
||||
|
||||
if ( toAdd.length !== 0 ) {
|
||||
ubolLog(`Registered ${toAdd.map(v => v.id)} content (css/js)`);
|
||||
try {
|
||||
await browser.scripting.registerContentScripts(toAdd);
|
||||
localRemove('$scripting.registerContentScripts');
|
||||
} catch(reason) {
|
||||
ubolErr(`registerContentScripts/${reason}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,46 @@ export const webext = self.browser;
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
// Workaround for:
|
||||
// https://github.com/uBlockOrigin/uBOL-home/issues/515
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=300236
|
||||
//
|
||||
// For each realm, we will force-reload registered rulesets once.
|
||||
|
||||
const { windows } = webext;
|
||||
const NORMAL_REALM = 0b01;
|
||||
const PRIVATE_REALM = 0b10;
|
||||
const ALL_REALMS = NORMAL_REALM | PRIVATE_REALM;
|
||||
|
||||
let seenRealms = 0b00;
|
||||
let seenRealmsReady = webext.storage.session.get('safari.seenRealms').then(bin => {
|
||||
seenRealms |= bin?.['safari.seenRealms'] ?? 0;
|
||||
}).catch(( ) => {
|
||||
});
|
||||
|
||||
async function forceEnableRulesets(windowId) {
|
||||
await seenRealmsReady;
|
||||
if ( seenRealms === ALL_REALMS ) { return; }
|
||||
if ( windowId === windows.WINDOW_ID_NONE ) { return; }
|
||||
const details = await windows.get(windowId, { windowTypes: [ 'normal' ] });
|
||||
const incognito = details?.incognito;
|
||||
if ( typeof incognito !== 'boolean' ) { return; }
|
||||
const currentRealm = incognito ? PRIVATE_REALM : NORMAL_REALM;
|
||||
if ( (seenRealms & currentRealm) !== 0 ) { return; }
|
||||
seenRealms |= currentRealm;
|
||||
webext.storage.session.set({ 'safari.seenRealms': seenRealms });
|
||||
const ids = await nativeDNR.getEnabledRulesets();
|
||||
if ( ids.length === 0 ) { return; }
|
||||
nativeDNR.updateEnabledRulesets({
|
||||
disableRulesetIds: ids.slice(),
|
||||
enableRulesetIds: ids.slice(),
|
||||
});
|
||||
}
|
||||
|
||||
windows.onFocusChanged.addListener(forceEnableRulesets);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/declarativeNetRequest/
|
||||
|
||||
const nativeDNR = webext.declarativeNetRequest;
|
||||
|
|
@ -142,8 +182,10 @@ export const dnr = {
|
|||
if ( optionsAfter === undefined ) { return; }
|
||||
return nativeDNR.updateDynamicRules(optionsAfter);
|
||||
},
|
||||
updateEnabledRulesets(...args) {
|
||||
return nativeDNR.updateEnabledRulesets(...args);
|
||||
async updateEnabledRulesets(...args) {
|
||||
await nativeDNR.updateEnabledRulesets(...args);
|
||||
seenRealms = 0b00;
|
||||
await webext.storage.session.remove('safari.seenRealms');
|
||||
},
|
||||
async updateSessionRules(optionsBefore) {
|
||||
const optionsAfter = prepareUpdateRules(optionsBefore);
|
||||
|
|
@ -181,44 +223,3 @@ export const dnr = {
|
|||
return nativeDNR.setExtensionActionOptions(...args);
|
||||
},
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Workaround for:
|
||||
// https://github.com/uBlockOrigin/uBOL-home/issues/515
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=300236
|
||||
//
|
||||
// For each realm, we will force-reload registered rulesets once.
|
||||
|
||||
const { windows } = webext;
|
||||
const NORMAL_REALM = 0b01;
|
||||
const PRIVATE_REALM = 0b10;
|
||||
const ALL_REALMS = NORMAL_REALM | PRIVATE_REALM;
|
||||
let seenRealms = 0b00;
|
||||
|
||||
async function forceEnableRulesets(windowId) {
|
||||
await seenRealmsReady;
|
||||
if ( seenRealms === ALL_REALMS ) { return; }
|
||||
if ( windowId === windows.WINDOW_ID_NONE ) { return; }
|
||||
const details = await windows.get(windowId, { windowTypes: [ 'normal' ] });
|
||||
const incognito = details?.incognito;
|
||||
if ( typeof incognito !== 'boolean' ) { return; }
|
||||
const currentRealm = incognito ? PRIVATE_REALM : NORMAL_REALM;
|
||||
if ( (seenRealms & currentRealm) !== 0 ) { return; }
|
||||
seenRealms |= currentRealm;
|
||||
webext.storage.session.set({ 'safari.seenRealms': seenRealms });
|
||||
const ids = await nativeDNR.getEnabledRulesets();
|
||||
if ( ids.length === 0 ) { return; }
|
||||
nativeDNR.updateEnabledRulesets({
|
||||
disableRulesetIds: ids.slice(),
|
||||
enableRulesetIds: ids.slice(),
|
||||
});
|
||||
}
|
||||
|
||||
windows.onFocusChanged.addListener(forceEnableRulesets);
|
||||
|
||||
const seenRealmsReady = webext.storage.session.get('safari.seenRealms').then(bin => {
|
||||
seenRealms |= bin?.['safari.seenRealms'] ?? 0;
|
||||
}).catch(( ) => {
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue