Add support for `elemhide` (through `specifichide`)

Related documentation:
- https://help.eyeo.com/en/adblockplus/how-to-write-filters#element-hiding

Related feedback/discussion:
- https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/

The `elemhide` filter option as per ABP semantic is
now supported. Previously uBO would consider `elemhide`
to be an alias of `generichide`.

The support of `elemhide` is through the convenient
conversion of `elemhide` option into existing
`generichide` option and new `specifichide` option.

The purpose of the new `specifichide` filter option
is to disable all specific cosmetic filters, i.e.
those who target a specific site.

Additionally, for convenience purpose, the filter
options `generichide`, `specifichide` and `elemhide`
can be aliased using the shorter forms `ghide`,
`shide` and `ehide` respectively.
This commit is contained in:
Raymond Hill 2019-09-21 11:30:38 -04:00
parent 6033ebf0d0
commit 23c4c80136
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
9 changed files with 269 additions and 141 deletions

View File

@ -33,7 +33,7 @@ if ( vAPI.webextFlavor === undefined ) {
/******************************************************************************/
const µBlock = (function() { // jshint ignore:line
const µBlock = (( ) => { // jshint ignore:line
const hiddenSettingsDefault = {
allowGenericProceduralFilters: false,
@ -84,7 +84,7 @@ const µBlock = (function() { // jshint ignore:line
requestLogMaxEntries: 1000,
showIconBadge: true,
tooltipsDisabled: false,
webrtcIPAddressHidden: false
webrtcIPAddressHidden: false,
},
hiddenSettingsDefault: hiddenSettingsDefault,
@ -133,22 +133,22 @@ const µBlock = (function() { // jshint ignore:line
localSettings: {
blockedRequestCount: 0,
allowedRequestCount: 0
allowedRequestCount: 0,
},
localSettingsLastModified: 0,
localSettingsLastSaved: 0,
// Read-only
systemSettings: {
compiledMagic: 18, // Increase when compiled format changes
selfieMagic: 18 // Increase when selfie format changes
compiledMagic: 19, // Increase when compiled format changes
selfieMagic: 19, // Increase when selfie format changes
},
restoreBackupSettings: {
lastRestoreFile: '',
lastRestoreTime: 0,
lastBackupFile: '',
lastBackupTime: 0
lastBackupTime: 0,
},
commandShortcuts: new Map(),

View File

@ -23,7 +23,7 @@
/******************************************************************************/
µBlock.cosmeticFilteringEngine = (function(){
µBlock.cosmeticFilteringEngine = (( ) => {
/******************************************************************************/
@ -238,10 +238,12 @@ const FilterContainer = function() {
// is to prevent repeated allocation/deallocation overheads -- the
// constructors/destructors of javascript Set/Map is assumed to be costlier
// than just calling clear() on these.
this.setRegister0 = new Set();
this.setRegister1 = new Set();
this.setRegister2 = new Set();
this.mapRegister0 = new Map();
this.simpleSet$ = new Set();
this.complexSet$ = new Set();
this.specificSet$ = new Set();
this.exceptionSet$ = new Set();
this.proceduralSet$ = new Set();
this.dummySet$ = new Set();
this.reset();
};
@ -830,11 +832,11 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) {
//console.time('cosmeticFilteringEngine.retrieveGenericSelectors');
const simpleSelectors = this.setRegister0;
const complexSelectors = this.setRegister1;
const simpleSelectors = this.simpleSet$;
const complexSelectors = this.complexSet$;
const cacheEntry = this.selectorCache.get(request.hostname);
const previousHits = cacheEntry && cacheEntry.cosmetic || this.setRegister2;
const previousHits = cacheEntry && cacheEntry.cosmetic || this.dummySet$;
for ( const type in this.lowlyGeneric ) {
const entry = this.lowlyGeneric[type];
@ -891,6 +893,10 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) {
excepted,
};
// Important: always clear used registers before leaving.
simpleSelectors.clear();
complexSelectors.clear();
// Cache and inject (if user stylesheets supported) looked-up low generic
// cosmetic filters.
if (
@ -931,10 +937,6 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) {
});
}
// Important: always clear used registers before leaving.
this.setRegister0.clear();
this.setRegister1.clear();
//console.timeEnd('cosmeticFilteringEngine.retrieveGenericSelectors');
return out;
@ -946,8 +948,6 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
request,
options
) {
//console.time('cosmeticFilteringEngine.retrieveSpecificSelectors');
const hostname = request.hostname;
const cacheEntry = this.selectorCache.get(hostname);
@ -976,7 +976,11 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
};
if ( options.noCosmeticFiltering !== true ) {
const specificSet = this.setRegister1;
const specificSet = this.specificSet$;
const proceduralSet = this.proceduralSet$;
const exceptionSet = this.exceptionSet$;
const dummySet = this.dummySet$;
// Cached cosmetic filters: these are always declarative.
if ( cacheEntry !== undefined ) {
cacheEntry.retrieve('cosmetic', specificSet);
@ -986,17 +990,30 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
}
}
const exceptionSet = this.setRegister0;
const proceduralSet = this.setRegister2;
// Retrieve filters with a non-empty hostname
this.specificFilters.retrieve(
hostname,
[ specificSet, exceptionSet, proceduralSet, exceptionSet ]
options.noSpecificCosmeticFiltering !== true
? [ specificSet, exceptionSet, proceduralSet, exceptionSet ]
: [ dummySet, exceptionSet, dummySet, exceptionSet ],
1
);
// Retrieve filters with an empty hostname
this.specificFilters.retrieve(
hostname,
options.noGenericCosmeticFiltering !== true
? [ specificSet, exceptionSet, proceduralSet, exceptionSet ]
: [ dummySet, exceptionSet, dummySet, exceptionSet ],
2
);
// Retrieve filters with a non-empty entity
if ( request.entity !== '' ) {
this.specificFilters.retrieve(
`${hostname.slice(0, -request.domain.length)}${request.entity}`,
[ specificSet, exceptionSet, proceduralSet, exceptionSet ]
options.noSpecificCosmeticFiltering !== true
? [ specificSet, exceptionSet, proceduralSet, exceptionSet ]
: [ dummySet, exceptionSet, dummySet, exceptionSet ],
1
);
}
@ -1060,9 +1077,10 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
}
// Important: always clear used registers before leaving.
this.setRegister0.clear();
this.setRegister1.clear();
this.setRegister2.clear();
specificSet.clear();
proceduralSet.clear();
exceptionSet.clear();
dummySet.clear();
}
// CSS selectors for collapsible blocked elements
@ -1115,8 +1133,6 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
}
}
//console.timeEnd('cosmeticFilteringEngine.retrieveSpecificSelectors');
return out;
};

View File

@ -500,81 +500,34 @@ vAPI.messaging.listen({
const µb = µBlock;
const onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
default:
break;
const retrieveContentScriptParameters = function(senderDetails, request) {
const { url, tabId, frameId } = senderDetails;
if ( url === undefined || tabId === undefined || frameId === undefined ) {
return;
}
if ( request.url !== url ) { return; }
const pageStore = µb.pageStoreFromTabId(tabId);
if ( pageStore === null || pageStore.getNetFilteringSwitch() === false ) {
return;
}
// Sync
let response,
tabId, frameId,
pageStore = null;
if ( sender && sender.tab ) {
tabId = sender.tab.id;
frameId = sender.frameId;
pageStore = µb.pageStoreFromTabId(tabId);
}
switch ( request.what ) {
case 'cosmeticFiltersInjected':
µb.cosmeticFilteringEngine.addToSelectorCache(request);
break;
case 'getCollapsibleBlockedRequests':
response = {
id: request.id,
hash: request.hash,
netSelectorCacheCountMax:
µb.cosmeticFilteringEngine.netSelectorCacheCountMax
};
if (
µb.userSettings.collapseBlocked &&
pageStore &&
pageStore.getNetFilteringSwitch()
) {
pageStore.getBlockedResources(request, response);
}
break;
case 'maybeGoodPopup':
µb.maybeGoodPopup.tabId = tabId;
µb.maybeGoodPopup.url = request.url;
break;
case 'shouldRenderNoscriptTags':
if ( pageStore === null ) { break; }
const fctxt = µb.filteringContext.fromTabId(tabId);
if ( pageStore.filterScripting(fctxt, undefined) ) {
vAPI.tabs.executeScript(tabId, {
file: '/js/scriptlets/noscript-spoof.js',
frameId: frameId,
runAt: 'document_end'
});
}
break;
case 'retrieveContentScriptParameters':
if (
pageStore === null ||
pageStore.getNetFilteringSwitch() === false ||
!request.url
) {
break;
}
const noCosmeticFiltering = pageStore.noCosmeticFiltering === true;
response = {
const response = {
collapseBlocked: µb.userSettings.collapseBlocked,
noCosmeticFiltering,
noGenericCosmeticFiltering: noCosmeticFiltering,
noSpecificCosmeticFiltering: noCosmeticFiltering,
};
// https://github.com/uBlockOrigin/uAssets/issues/5704
// `generichide` must be evaluated in the frame context.
if ( noCosmeticFiltering === false ) {
const genericHide =
µb.staticNetFilteringEngine.matchStringGenericHide(request.url);
µb.staticNetFilteringEngine.matchStringElementHide(
'generic',
request.url
);
response.noGenericCosmeticFiltering = genericHide === 2;
if ( genericHide !== 0 && µb.logger.enabled ) {
µBlock.filteringContext
@ -587,29 +540,117 @@ const onMessage = function(request, sender, callback) {
.toLogger();
}
}
request.tabId = tabId;
request.frameId = frameId;
request.hostname = µb.URI.hostnameFromURI(request.url);
request.domain = µb.URI.domainFromHostname(request.hostname);
request.entity = µb.URI.entityFromDomain(request.domain);
response.specificCosmeticFilters =
µb.cosmeticFilteringEngine.retrieveSpecificSelectors(
request,
response
// https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/
// Add support for `specifichide`.
if ( noCosmeticFiltering === false ) {
const specificHide =
µb.staticNetFilteringEngine.matchStringElementHide(
'specific',
request.url
);
response.noSpecificCosmeticFiltering = specificHide === 2;
if ( specificHide !== 0 && µb.logger.enabled ) {
µBlock.filteringContext
.duplicate()
.fromTabId(tabId)
.setURL(request.url)
.setRealm('network')
.setType('specifichide')
.setFilter(µb.staticNetFilteringEngine.toLogData())
.toLogger();
}
}
// Cosmetic filtering can be effectively disabled when both specific and
// generic cosmetic filtering are disabled.
if (
noCosmeticFiltering === false &&
response.noGenericCosmeticFiltering &&
response.noSpecificCosmeticFiltering
) {
response.noCosmeticFiltering = true;
}
response.specificCosmeticFilters =
µb.cosmeticFilteringEngine.retrieveSpecificSelectors(request, response);
if ( µb.canInjectScriptletsNow === false ) {
response.scriptlets = µb.scriptletFilteringEngine.retrieve(request);
}
if ( µb.logger.enabled && response.noCosmeticFiltering !== true ) {
µb.logCosmeticFilters(tabId, frameId);
}
return response;
};
const onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
default:
break;
}
const senderDetails = µb.getMessageSenderDetails(sender);
const pageStore = µb.pageStoreFromTabId(senderDetails.tabId);
// Sync
let response;
switch ( request.what ) {
case 'cosmeticFiltersInjected':
µb.cosmeticFilteringEngine.addToSelectorCache(request);
break;
case 'getCollapsibleBlockedRequests':
response = {
id: request.id,
hash: request.hash,
netSelectorCacheCountMax:
µb.cosmeticFilteringEngine.netSelectorCacheCountMax,
};
if (
µb.userSettings.collapseBlocked &&
pageStore && pageStore.getNetFilteringSwitch()
) {
pageStore.getBlockedResources(request, response);
}
break;
case 'maybeGoodPopup':
µb.maybeGoodPopup.tabId = senderDetails.tabId;
µb.maybeGoodPopup.url = request.url;
break;
case 'shouldRenderNoscriptTags':
if ( pageStore === null ) { break; }
const fctxt = µb.filteringContext.fromTabId(senderDetails.tabId);
if ( pageStore.filterScripting(fctxt, undefined) ) {
vAPI.tabs.executeScript(senderDetails.tabId, {
file: '/js/scriptlets/noscript-spoof.js',
frameId: senderDetails.frameId,
runAt: 'document_end',
});
}
break;
case 'retrieveContentScriptParameters':
response = retrieveContentScriptParameters(senderDetails, request);
break;
case 'retrieveGenericCosmeticSelectors':
request.tabId = tabId;
request.frameId = frameId;
request.tabId = senderDetails.tabId;
request.frameId = senderDetails.frameId;
response = {
result: µb.cosmeticFilteringEngine.retrieveGenericSelectors(request)
result: µb.cosmeticFilteringEngine.retrieveGenericSelectors(request),
};
break;

View File

@ -176,17 +176,18 @@ const fromCosmeticFilter = function(details) {
let end = content.indexOf('\n', pos);
if ( end === -1 ) { end = content.length; }
pos = end;
let fargs = JSON.parse(content.slice(beg, end));
const fargs = JSON.parse(content.slice(beg, end));
const filterType = fargs[0];
// https://github.com/gorhill/uBlock/issues/2763
if ( fargs[0] >= 0 && fargs[0] <= 5 && details.ignoreGeneric ) {
if ( filterType >= 0 && filterType <= 5 && details.ignoreGeneric ) {
continue;
}
// Do not confuse cosmetic filters with HTML ones.
if ( (fargs[0] === 64) !== isHtmlFilter ) { continue; }
if ( (filterType === 64) !== isHtmlFilter ) { continue; }
switch ( fargs[0] ) {
switch ( filterType ) {
// Lowly generic cosmetic filters
case 0: // simple id-based
if (
@ -232,9 +233,17 @@ const fromCosmeticFilter = function(details) {
) {
break;
}
if ( hostnameMatches(fargs[1]) ) {
found = fargs[1] + prefix + selector;
if ( hostnameMatches(fargs[1]) === false ) { break; }
// https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/
// Ignore match if specific cosmetic filters are disabled
if (
filterType === 8 &&
exception === false &&
details.ignoreSpecific
) {
break;
}
found = fargs[1] + prefix + selector;
break;
// Scriptlet injection
case 32:

View File

@ -170,8 +170,16 @@ const fromCosmeticFilter = async function(details, callback) {
id: id,
domain: µBlock.URI.domainFromHostname(hostname),
hostname: hostname,
ignoreGeneric: µBlock.staticNetFilteringEngine
.matchStringGenericHide(details.url) === 2,
ignoreGeneric:
µBlock.staticNetFilteringEngine.matchStringElementHide(
'generic',
details.url
) === 2,
ignoreSpecific:
µBlock.staticNetFilteringEngine.matchStringElementHide(
'specific',
details.url
) === 2,
rawFilter: details.rawFilter
});
};

View File

@ -44,7 +44,7 @@
optional.
The static extended filtering engine also offers parsing capabilities which
are available to all other specialized fitlering engines. For example,
are available to all other specialized filtering engines. For example,
cosmetic and html filtering can ask the extended filtering engine to
compile/validate selectors.
@ -303,7 +303,7 @@
[ ':nth-ancestor', compileNthAncestorSelector ],
[ ':spath', compileSpathExpression ],
[ ':watch-attr', compileAttrList ],
[ ':xpath', compileXpathExpression ]
[ ':xpath', compileXpathExpression ],
]);
// https://github.com/gorhill/uBlock/issues/2793#issuecomment-333269387
@ -517,7 +517,11 @@
this.timer = undefined;
this.strToIdMap = new Map();
this.hostnameToSlotIdMap = new Map();
this.hostnameSlots = [];
// Avoid heterogeneous arrays. Thus:
this.hostnameSlots = []; // array of integers
// IMPORTANT: initialize with an empty array because -0 is NOT < 0.
this.hostnameSlotsEx = [ [] ]; // Array of arrays of integers
// Array of strings (selectors and pseudo-selectors)
this.strSlots = [];
this.size = 0;
if ( selfie !== undefined ) {
@ -543,24 +547,27 @@
this.hostnameSlots.push(strId);
return;
}
const bucket = this.hostnameSlots[iHn];
if ( Array.isArray(bucket) ) {
bucket.push(strId);
} else {
this.hostnameSlots[iHn] = [ bucket, strId ];
if ( iHn < 0 ) {
this.hostnameSlotsEx[-iHn].push(strId);
return;
}
const strIdEx = -this.hostnameSlotsEx.length;
this.hostnameToSlotIdMap.set(hn, strIdEx);
this.hostnameSlotsEx.push([ this.hostnameSlots[iHn], strId ]);
this.hostnameSlots[iHn] = strIdEx;
}
clear() {
this.hostnameToSlotIdMap.clear();
this.hostnameSlots.length = 0;
this.hostnameSlotsEx.length = 1; // IMPORTANT: 1, not 0
this.strSlots.length = 0;
this.strToIdMap.clear();
this.size = 0;
}
collectGarbage(async = false) {
if ( async === false ) {
collectGarbage(later = false) {
if ( later === false ) {
if ( this.timer !== undefined ) {
self.cancelIdleCallback(this.timer);
this.timer = undefined;
@ -578,23 +585,39 @@
);
}
retrieve(hostname, out) {
// modifiers = 1: return only specific items
// modifiers = 2: return only generic items
//
retrieve(hostname, out, modifiers = 0) {
if ( modifiers === 2 ) {
hostname = '';
}
const mask = out.length - 1; // out.length must be power of two
for (;;) {
const filterId = this.hostnameToSlotIdMap.get(hostname);
if ( filterId !== undefined ) {
const bucket = this.hostnameSlots[filterId];
if ( Array.isArray(bucket) ) {
for ( const id of bucket ) {
out[id & mask].add(this.strSlots[id >>> this.nBits]);
if ( filterId < 0 ) {
const bucket = this.hostnameSlotsEx[-filterId];
for ( const strId of bucket ) {
out[strId & mask].add(
this.strSlots[strId >>> this.nBits]
);
}
} else {
out[bucket & mask].add(this.strSlots[bucket >>> this.nBits]);
const strId = this.hostnameSlots[filterId];
out[strId & mask].add(
this.strSlots[strId >>> this.nBits]
);
}
}
if ( hostname === '' ) { break; }
const pos = hostname.indexOf('.');
hostname = pos !== -1 ? hostname.slice(pos + 1) : '';
if ( pos === -1 ) {
if ( modifiers === 1 ) { break; }
hostname = '';
} else {
hostname = hostname.slice(pos + 1);
}
}
}
@ -602,6 +625,7 @@
return {
hostnameToSlotIdMap: Array.from(this.hostnameToSlotIdMap),
hostnameSlots: this.hostnameSlots,
hostnameSlotsEx: this.hostnameSlotsEx,
strSlots: this.strSlots,
size: this.size
};
@ -610,6 +634,7 @@
fromSelfie(selfie) {
this.hostnameToSlotIdMap = new Map(selfie.hostnameToSlotIdMap);
this.hostnameSlots = selfie.hostnameSlots;
this.hostnameSlotsEx = selfie.hostnameSlotsEx;
this.strSlots = selfie.strSlots;
this.size = selfie.size;
}

View File

@ -68,12 +68,13 @@ const typeNameToTypeValue = {
'popunder': 12 << 4,
'main_frame': 13 << 4, // start of 1st-party-only behavorial filtering
'generichide': 14 << 4,
'inline-font': 15 << 4,
'inline-script': 16 << 4,
'data': 17 << 4, // special: a generic data holder
'redirect': 18 << 4,
'webrtc': 19 << 4,
'unsupported': 20 << 4
'specifichide': 15 << 4,
'inline-font': 16 << 4,
'inline-script': 17 << 4,
'data': 18 << 4, // special: a generic data holder
'redirect': 19 << 4,
'webrtc': 20 << 4,
'unsupported': 21 << 4,
};
const otherTypeBitValue = typeNameToTypeValue.other;
@ -110,12 +111,13 @@ const typeValueToTypeName = {
12: 'popunder',
13: 'document',
14: 'generichide',
15: 'inline-font',
16: 'inline-script',
17: 'data',
18: 'redirect',
19: 'webrtc',
20: 'unsupported'
15: 'specifichide',
16: 'inline-font',
17: 'inline-script',
18: 'data',
19: 'redirect',
20: 'webrtc',
21: 'unsupported'
};
const BlockImportant = BlockAction | Important;
@ -1848,11 +1850,11 @@ FilterParser.prototype.toNormalizedType = {
'data': 'data',
'doc': 'main_frame',
'document': 'main_frame',
'elemhide': 'generichide',
'font': 'font',
'frame': 'sub_frame',
'genericblock': 'unsupported',
'generichide': 'generichide',
'ghide': 'generichide',
'image': 'image',
'inline-font': 'inline-font',
'inline-script': 'inline-script',
@ -1864,6 +1866,8 @@ FilterParser.prototype.toNormalizedType = {
'popunder': 'popunder',
'popup': 'popup',
'script': 'script',
'specifichide': 'specifichide',
'shide': 'specifichide',
'stylesheet': 'stylesheet',
'subdocument': 'sub_frame',
'xhr': 'xmlhttprequest',
@ -2017,7 +2021,8 @@ FilterParser.prototype.parseOptions = function(s) {
this.dataStr = '';
continue;
}
// Used by Adguard, purpose is unclear -- just ignore for now.
// Used by Adguard:
// https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters?aid=16593#empty-modifier
if ( opt === 'empty' || opt === 'mp4' ) {
if ( this.redirect !== 0 ) {
this.unsupported = true;
@ -2031,6 +2036,13 @@ FilterParser.prototype.parseOptions = function(s) {
this.badFilter = true;
continue;
}
// https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/
// Add support for `elemhide`. Rarely used but it happens.
if ( opt === 'elemhide' || opt === 'ehide' ) {
this.parseTypeOption('specifichide', not);
this.parseTypeOption('generichide', not);
continue;
}
// Unrecognized filter option: ignore whole filter.
this.unsupported = true;
break;
@ -3055,17 +3067,19 @@ FilterContainer.prototype.realmMatchString = function(
// filter if and only if there was a hit on an exception filter.
// https://github.com/gorhill/uBlock/issues/2103
// User may want to override `generichide` exception filters.
// https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/
// Add support for `specifichide`.
FilterContainer.prototype.matchStringGenericHide = function(requestURL) {
const typeBits = typeNameToTypeValue['generichide'] | 0x80000000;
FilterContainer.prototype.matchStringElementHide = function(type, url) {
const typeBits = typeNameToTypeValue[`${type}hide`] | 0x80000000;
// Prime tokenizer: we get a normalized URL in return.
urlRegister = this.urlTokenizer.setURL(requestURL);
urlRegister = this.urlTokenizer.setURL(url);
this.filterRegister = null;
// These registers will be used by various filters
pageHostnameRegister = requestHostnameRegister =
µb.URI.hostnameFromURI(requestURL);
µb.URI.hostnameFromURI(url);
// Exception filters
if ( this.realmMatchString(AllowAction, typeBits, FirstParty) ) {

View File

@ -707,3 +707,18 @@
window.dispatchEvent(new CustomEvent(name));
}
};
/******************************************************************************/
µBlock.getMessageSenderDetails = function(sender) {
const r = {};
if ( sender instanceof Object ) {
r.url = sender.url;
r.frameId = sender.frameId;
const tab = sender.tab;
if ( tab instanceof Object ) {
r.tabId = tab.id;
}
}
return r;
};

View File

@ -61,7 +61,7 @@
<div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span>
<span style="flex-direction: column;">
<div style="margin-bottom: 1px;"><span data-filtex="\t(?:css|(?:inline-)?font)\t">css/font</span><span data-filtex="\timage\t">image</span><span data-filtex="\tmedia\t">media</span><span data-filtex="\t(?:inline-)?script(?:ing)?\t">script</span></div>
<div><span data-filtex="\t(?:websocket|xhr)\t">xhr</span><span data-filtex="\tframe\t">frame</span><span data-filtex="\t(?:beacon|csp_report|ping|other)\t">other</span><span data-filtex="\t(?:dom|generichide)\t">dom</span></div>
<div><span data-filtex="\t(?:websocket|xhr)\t">xhr</span><span data-filtex="\tframe\t">frame</span><span data-filtex="\t(?:beacon|csp_report|ping|other)\t">other</span><span data-filtex="\t(?:dom|g(?:eneric)?hide|s(?:pecific)?hide)\t">dom</span></div>
</span>
</div>
<div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\t(?:0,)?1\t" data-i18n="loggerRowFiltererBuiltin1p"></span><span data-filtex="\t(?:3(?:,\d)?|0,3)\t" data-i18n="loggerRowFiltererBuiltin3p"></span></div>