Skip to content

Commit c166cb7

Browse files
Oleg Labudkoartemiv4nov
Oleg Labudko
authored andcommitted
Pull request #83: AG-17273 Consider :has() as a standard pseudo-class if ExtendedCss usage is not forced by #?# rule marker
Merge in ADGUARD-IOS/safari-converter from feature/AG-17273 to master * commit 'aa8b1a334d6ede45c7bc1adbc305bd5adf9b5d2b': fixed SafariService added test to CosmeticRuleTests fixed performance test check safari version to handle pseudo-class :has() fixed testPerformanceSingleRun and testPerformance fixed testCompileExtendedCssRule handle rules with ## marker and :has() as simple rules
2 parents eabd10c + aa8b1a3 commit c166cb7

File tree

6 files changed

+85
-13
lines changed

6 files changed

+85
-13
lines changed

Sources/ContentBlockerConverter/Compiler/BlockerEntryFactory.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ class BlockerEntryFactory {
282282
var rawAdded = false
283283
if rule.hasContentType(contentType: NetworkRule.ContentType.XMLHTTPREQUEST) {
284284
// `fetch` resource type is supported since Safari 15
285-
if SafariService.current.version.isSafari15() {
285+
if SafariService.current.version.isSafari15orGreater() {
286286
types.append("fetch")
287287
} else if !rawAdded {
288288
rawAdded = true
@@ -292,7 +292,7 @@ class BlockerEntryFactory {
292292

293293
if rule.hasContentType(contentType: NetworkRule.ContentType.OTHER) {
294294
// `other` resource type is supported since Safari 15
295-
if SafariService.current.version.isSafari15() {
295+
if SafariService.current.version.isSafari15orGreater() {
296296
types.append("other")
297297
} else if !rawAdded {
298298
rawAdded = true
@@ -302,7 +302,7 @@ class BlockerEntryFactory {
302302

303303
if rule.hasContentType(contentType: NetworkRule.ContentType.WEBSOCKET) {
304304
// `websocket` resource type is supported since Safari 15
305-
if SafariService.current.version.isSafari15() {
305+
if SafariService.current.version.isSafari15orGreater() {
306306
types.append("websocket")
307307
} else if !rawAdded {
308308
rawAdded = true
@@ -326,7 +326,7 @@ class BlockerEntryFactory {
326326
types.append("document")
327327
}
328328
if !documentAdded && rule.hasContentType(contentType: NetworkRule.ContentType.SUBDOCUMENT) {
329-
if !SafariService.current.version.isSafari15() {
329+
if !SafariService.current.version.isSafari15orGreater() {
330330
documentAdded = true
331331
types.append("document")
332332
}
@@ -358,7 +358,7 @@ class BlockerEntryFactory {
358358
private func addLoadContext(rule: NetworkRule, trigger: inout BlockerEntry.Trigger) -> Void {
359359
var context = [String]();
360360
// `child-frame` and `top-frame` contexts are supported since Safari 15
361-
if SafariService.current.version.isSafari15() {
361+
if SafariService.current.version.isSafari15orGreater() {
362362
if rule.hasContentType(contentType: NetworkRule.ContentType.SUBDOCUMENT)
363363
&& !rule.isContentType(contentType:NetworkRule.ContentType.ALL) {
364364
context.append("child-frame");

Sources/ContentBlockerConverter/Rules/CosmeticRule.swift

+8-1
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import Foundation
44
* Cosmetic rule class
55
*/
66
class CosmeticRule: Rule {
7+
private static let EXT_CSS_PSEUDO_INDICATOR_HAS = ":has("
78
/**
89
* Pseudo class indicators. They are used to detect if rule is extended or not even if rule does not
910
* have extended css marker
1011
*/
1112
private static let EXT_CSS_PSEUDO_INDICATORS = [
12-
":has(",
13+
CosmeticRule.EXT_CSS_PSEUDO_INDICATOR_HAS,
1314
":has-text(",
1415
":contains(",
1516
":matches-css",
@@ -122,6 +123,12 @@ class CosmeticRule: Rule {
122123
} else if c == ":".utf16.first! {
123124
for indicator in CosmeticRule.EXT_CSS_PSEUDO_INDICATORS {
124125
if nsContent.substring(from: i).starts(with: indicator) {
126+
// the rule with `##` marker and `:has()` pseudo-class should not be considered as ExtendedCss,
127+
// because `:has()` pseudo-class has native implementation since Safari 16 (15.4)
128+
// https://github.com/AdguardTeam/SafariConverterLib/issues/43
129+
if indicator == EXT_CSS_PSEUDO_INDICATOR_HAS && SafariService.current.version.isSafari16orGreater() {
130+
continue
131+
}
125132
return true
126133
}
127134
}

Sources/ContentBlockerConverter/SafariService.swift

+9-6
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,23 @@ public enum SafariVersion: Int {
1111
case safari13 = 13;
1212
case safari14 = 14;
1313
case safari15 = 15;
14+
case safari16 = 16;
1415

1516
/**
1617
* Returns rules limit for current Safari version
1718
* Safari allows up to 50k rules by default, but starting from 15 version it allows up to 150k rules
1819
*/
1920
var rulesLimit: Int {
20-
switch self {
21-
case .safari11, .safari12, .safari13, .safari14: return 50000
22-
case .safari15: return 150000
23-
}
21+
self.rawValue >= SafariVersion.safari15.rawValue ? 150000 : 50000
22+
}
23+
24+
func isSafari15orGreater() -> Bool {
25+
return self.rawValue >= SafariVersion.safari15.rawValue;
26+
2427
}
2528

26-
func isSafari15() -> Bool {
27-
return self == SafariVersion.safari15;
29+
func isSafari16orGreater() -> Bool {
30+
return self.rawValue >= SafariVersion.safari16.rawValue;
2831
}
2932
}
3033

Tests/ContentBlockerConverterTests/AdvancedBlockingTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ final class AdvancedBlockingTests: XCTestCase {
317317
errorsCounter: ErrorsCounter()
318318
);
319319

320-
let rule = try! CosmeticRule(ruleText: "test.com##.content:has(> .test_selector)");
320+
let rule = try! CosmeticRule(ruleText: "test.com#?#.content:has(> .test_selector)");
321321
let result = compiler.compileRules(rules: [rule as Rule]);
322322

323323
XCTAssertNotNil(result)

Tests/ContentBlockerConverterTests/ContentBlockerConverterTests.swift

+43
Original file line numberDiff line numberDiff line change
@@ -2263,6 +2263,48 @@ final class ContentBlockerConverterTests: XCTestCase {
22632263
XCTAssertEqual(decoded[0].trigger.loadType, ["third-party"]);
22642264
XCTAssertEqual(decoded[0].action.type, "ignore-previous-rules");
22652265
}
2266+
2267+
func testConvertRulesWithPseudoClassHas() {
2268+
let rules = [
2269+
"test.com##div:has(.banner)",
2270+
"example.org#?#div:has(.adv)",
2271+
]
2272+
2273+
var result = converter.convertArray(rules: rules, safariVersion: .safari15 ,advancedBlocking: true)
2274+
XCTAssertEqual(result.convertedCount, 0)
2275+
XCTAssertEqual(result.advancedBlockingConvertedCount, 2)
2276+
XCTAssertEqual(result.totalConvertedCount, 2)
2277+
XCTAssertEqual(result.errorsCount, 0)
2278+
2279+
let decoded = try! parseJsonString(json: result.advancedBlocking!);
2280+
XCTAssertEqual(decoded.count, 2);
2281+
2282+
XCTAssertEqual(decoded[0].trigger.ifDomain, ["*test.com"]);
2283+
XCTAssertEqual(decoded[0].action.type, "css-extended");
2284+
XCTAssertEqual(decoded[0].action.css, "div:has(.banner)");
2285+
2286+
XCTAssertEqual(decoded[1].trigger.ifDomain, ["*example.org"]);
2287+
XCTAssertEqual(decoded[1].action.type, "css-extended");
2288+
XCTAssertEqual(decoded[1].action.css, "div:has(.adv)");
2289+
2290+
result = converter.convertArray(rules: rules, safariVersion: .safari16 ,advancedBlocking: true)
2291+
XCTAssertEqual(result.convertedCount, 1)
2292+
XCTAssertEqual(result.advancedBlockingConvertedCount, 1)
2293+
XCTAssertEqual(result.totalConvertedCount, 2)
2294+
XCTAssertEqual(result.errorsCount, 0)
2295+
2296+
let decodedSimpleRules = try! parseJsonString(json: result.converted);
2297+
XCTAssertEqual(decodedSimpleRules.count, 1);
2298+
XCTAssertEqual(decodedSimpleRules[0].trigger.ifDomain, ["*test.com"]);
2299+
XCTAssertEqual(decodedSimpleRules[0].action.type, "css-display-none");
2300+
XCTAssertEqual(decodedSimpleRules[0].action.selector, "div:has(.banner)");
2301+
2302+
let decodedAdvancedRules = try! parseJsonString(json: result.advancedBlocking!);
2303+
XCTAssertEqual(decodedAdvancedRules.count, 1);
2304+
XCTAssertEqual(decodedAdvancedRules[0].trigger.ifDomain, ["*example.org"]);
2305+
XCTAssertEqual(decodedAdvancedRules[0].action.type, "css-extended");
2306+
XCTAssertEqual(decodedAdvancedRules[0].action.css, "div:has(.adv)");
2307+
}
22662308

22672309
static var allTests = [
22682310
("testEmpty", testEmpty),
@@ -2322,5 +2364,6 @@ final class ContentBlockerConverterTests: XCTestCase {
23222364
("testUnicodeRules", testUnicodeRules),
23232365
("testAdvancedCosmeticRulesWithDomainModifier", testAdvancedCosmeticRulesWithDomainModifier),
23242366
("testBlockingRulesWithNonAsciiCharacters", testBlockingRulesWithNonAsciiCharacters),
2367+
("testConvertRulesWithPseudoClassHas", testConvertRulesWithPseudoClassHas),
23252368
]
23262369
}

Tests/ContentBlockerConverterTests/Rules/CosmeticRuleTests.swift

+19
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,24 @@ final class CosmeticRuleTests: XCTestCase {
333333
XCTAssertEqual(result.content, "//scriptlet(\"abort-on-property-read\", \"alert\")");
334334
}
335335

336+
func testRulesWithPseudoClassHas() {
337+
SafariService.current.version = SafariVersion.safari15;
338+
339+
var result = try! CosmeticRule(ruleText: "##.banner:has(.ads)");
340+
XCTAssertEqual(result.isExtendedCss, true);
341+
342+
result = try! CosmeticRule(ruleText: "#?#.banner:has(.ads)");
343+
XCTAssertEqual(result.isExtendedCss, true);
344+
345+
SafariService.current.version = SafariVersion.safari16;
346+
347+
result = try! CosmeticRule(ruleText: "##.banner:has(.ads)");
348+
XCTAssertEqual(result.isExtendedCss, false);
349+
350+
result = try! CosmeticRule(ruleText: "#?#.banner:has(.ads)");
351+
XCTAssertEqual(result.isExtendedCss, true);
352+
}
353+
336354
static var allTests = [
337355
("testElemhidingRules", testElemhidingRules),
338356
("testElemhidingRulesWhitelist", testElemhidingRulesWhitelist),
@@ -345,6 +363,7 @@ final class CosmeticRuleTests: XCTestCase {
345363
("testScriptletRulesWhitelist", testScriptletRulesWhitelist),
346364
("testDomains", testDomains),
347365
("testGenericWildcardRules", testGenericWildcardRules),
366+
("testRulesWithPseudoClassHas", testRulesWithPseudoClassHas),
348367
]
349368
}
350369

0 commit comments

Comments
 (0)