15 |
15 |
16 class AdBlockMatcher(QObject): |
16 class AdBlockMatcher(QObject): |
17 """ |
17 """ |
18 Class implementing the AdBlock matcher. |
18 Class implementing the AdBlock matcher. |
19 """ |
19 """ |
|
20 |
20 def __init__(self, manager): |
21 def __init__(self, manager): |
21 """ |
22 """ |
22 Constructor |
23 Constructor |
23 |
24 |
24 @param manager reference to the AdBlock manager object |
25 @param manager reference to the AdBlock manager object |
25 @type AdBlockManager |
26 @type AdBlockManager |
26 """ |
27 """ |
27 super().__init__(manager) |
28 super().__init__(manager) |
28 |
29 |
29 self.__manager = manager |
30 self.__manager = manager |
30 |
31 |
31 self.__createdRules = [] |
32 self.__createdRules = [] |
32 self.__networkExceptionRules = [] |
33 self.__networkExceptionRules = [] |
33 self.__networkBlockRules = [] |
34 self.__networkBlockRules = [] |
34 self.__domainRestrictedCssRules = [] |
35 self.__domainRestrictedCssRules = [] |
35 self.__documentRules = [] |
36 self.__documentRules = [] |
36 self.__elemhideRules = [] |
37 self.__elemhideRules = [] |
37 |
38 |
38 self.__elementHidingRules = "" |
39 self.__elementHidingRules = "" |
39 self.__networkBlockTree = AdBlockSearchTree() |
40 self.__networkBlockTree = AdBlockSearchTree() |
40 self.__networkExceptionTree = AdBlockSearchTree() |
41 self.__networkExceptionTree = AdBlockSearchTree() |
41 |
42 |
42 def match(self, request, urlDomain, urlString): |
43 def match(self, request, urlDomain, urlString): |
43 """ |
44 """ |
44 Public method to match a request. |
45 Public method to match a request. |
45 |
46 |
46 @param request URL request to be matched |
47 @param request URL request to be matched |
47 @type QWebEngineUrlRequestInfo |
48 @type QWebEngineUrlRequestInfo |
48 @param urlDomain domain of the URL |
49 @param urlDomain domain of the URL |
49 @type str |
50 @type str |
50 @param urlString requested URL as a lowercase string |
51 @param urlString requested URL as a lowercase string |
53 @rtype AdBlockRule |
54 @rtype AdBlockRule |
54 """ |
55 """ |
55 # exception rules |
56 # exception rules |
56 if self.__networkExceptionTree.find(request, urlDomain, urlString): |
57 if self.__networkExceptionTree.find(request, urlDomain, urlString): |
57 return None |
58 return None |
58 |
59 |
59 for rule in self.__networkExceptionRules: |
60 for rule in self.__networkExceptionRules: |
60 if rule.networkMatch(request, urlDomain, urlString): |
61 if rule.networkMatch(request, urlDomain, urlString): |
61 return None |
62 return None |
62 |
63 |
63 # block rules |
64 # block rules |
64 rule = self.__networkBlockTree.find(request, urlDomain, urlString) |
65 rule = self.__networkBlockTree.find(request, urlDomain, urlString) |
65 if rule: |
66 if rule: |
66 return rule |
67 return rule |
67 |
68 |
68 for rule in self.__networkBlockRules: |
69 for rule in self.__networkBlockRules: |
69 if rule.networkMatch(request, urlDomain, urlString): |
70 if rule.networkMatch(request, urlDomain, urlString): |
70 return rule |
71 return rule |
71 |
72 |
72 return None |
73 return None |
73 |
74 |
74 def adBlockDisabledForUrl(self, url): |
75 def adBlockDisabledForUrl(self, url): |
75 """ |
76 """ |
76 Public method to check, if AdBlock is disabled for the given URL. |
77 Public method to check, if AdBlock is disabled for the given URL. |
77 |
78 |
78 @param url URL to check |
79 @param url URL to check |
79 @type QUrl |
80 @type QUrl |
80 @return flag indicating disabled state |
81 @return flag indicating disabled state |
81 @rtype bool |
82 @rtype bool |
82 """ |
83 """ |
83 return any(rule.urlMatch(url) for rule in self.__documentRules) |
84 return any(rule.urlMatch(url) for rule in self.__documentRules) |
84 |
85 |
85 def elemHideDisabledForUrl(self, url): |
86 def elemHideDisabledForUrl(self, url): |
86 """ |
87 """ |
87 Public method to check, if element hiding is disabled for the given |
88 Public method to check, if element hiding is disabled for the given |
88 URL. |
89 URL. |
89 |
90 |
90 @param url URL to check |
91 @param url URL to check |
91 @type QUrl |
92 @type QUrl |
92 @return flag indicating disabled state |
93 @return flag indicating disabled state |
93 @rtype bool |
94 @rtype bool |
94 """ |
95 """ |
95 if self.adBlockDisabledForUrl(url): |
96 if self.adBlockDisabledForUrl(url): |
96 return True |
97 return True |
97 |
98 |
98 return any(rule.urlMatch(url) for rule in self.__elemhideRules) |
99 return any(rule.urlMatch(url) for rule in self.__elemhideRules) |
99 |
100 |
100 def elementHidingRules(self): |
101 def elementHidingRules(self): |
101 """ |
102 """ |
102 Public method to get the element hiding rules. |
103 Public method to get the element hiding rules. |
103 |
104 |
104 @return element hiding rules |
105 @return element hiding rules |
105 @rtype str |
106 @rtype str |
106 """ |
107 """ |
107 return self.__elementHidingRules |
108 return self.__elementHidingRules |
108 |
109 |
109 def elementHidingRulesForDomain(self, domain): |
110 def elementHidingRulesForDomain(self, domain): |
110 """ |
111 """ |
111 Public method to get the element hiding rules for the given domain. |
112 Public method to get the element hiding rules for the given domain. |
112 |
113 |
113 @param domain domain name |
114 @param domain domain name |
114 @type str |
115 @type str |
115 @return element hiding rules |
116 @return element hiding rules |
116 @rtype str |
117 @rtype str |
117 """ |
118 """ |
118 rules = "" |
119 rules = "" |
119 addedRulesCount = 0 |
120 addedRulesCount = 0 |
120 |
121 |
121 for rule in self.__domainRestrictedCssRules: |
122 for rule in self.__domainRestrictedCssRules: |
122 if not rule.matchDomain(domain): |
123 if not rule.matchDomain(domain): |
123 continue |
124 continue |
124 |
125 |
125 if addedRulesCount == 1000: |
126 if addedRulesCount == 1000: |
126 rules += rule.cssSelector() |
127 rules += rule.cssSelector() |
127 rules += "{display:none !important;}\n" |
128 rules += "{display:none !important;}\n" |
128 addedRulesCount = 0 |
129 addedRulesCount = 0 |
129 else: |
130 else: |
130 rules += rule.cssSelector() + "," |
131 rules += rule.cssSelector() + "," |
131 addedRulesCount += 1 |
132 addedRulesCount += 1 |
132 |
133 |
133 if addedRulesCount != 0: |
134 if addedRulesCount != 0: |
134 rules = rules[:-1] |
135 rules = rules[:-1] |
135 rules += "{display:none !important;}\n" |
136 rules += "{display:none !important;}\n" |
136 |
137 |
137 return rules |
138 return rules |
138 |
139 |
139 def update(self): |
140 def update(self): |
140 """ |
141 """ |
141 Public slot to update the internal state. |
142 Public slot to update the internal state. |
142 """ |
143 """ |
143 self.clear() |
144 self.clear() |
144 |
145 |
145 cssRulesDict = {} |
146 cssRulesDict = {} |
146 exceptionCssRules = [] |
147 exceptionCssRules = [] |
147 |
148 |
148 for subscription in self.__manager.subscriptions(): |
149 for subscription in self.__manager.subscriptions(): |
149 if subscription.isEnabled(): |
150 if subscription.isEnabled(): |
150 for rule in subscription.allRules(): |
151 for rule in subscription.allRules(): |
151 # Don't add internally disabled rules to the cache |
152 # Don't add internally disabled rules to the cache |
152 if rule.isInternalDisabled(): |
153 if rule.isInternalDisabled(): |
153 continue |
154 continue |
154 |
155 |
155 if rule.isCSSRule(): |
156 if rule.isCSSRule(): |
156 # Only enabled CSS rules are added to the cache because |
157 # Only enabled CSS rules are added to the cache because |
157 # there is no enabled/disabled check on match. They are |
158 # there is no enabled/disabled check on match. They are |
158 # directly embedded to pages. |
159 # directly embedded to pages. |
159 if not rule.isEnabled(): |
160 if not rule.isEnabled(): |
160 continue |
161 continue |
161 |
162 |
162 if rule.isException(): |
163 if rule.isException(): |
163 exceptionCssRules.append(rule) |
164 exceptionCssRules.append(rule) |
164 else: |
165 else: |
165 cssRulesDict[rule.cssSelector()] = rule |
166 cssRulesDict[rule.cssSelector()] = rule |
166 elif rule.isDocument(): |
167 elif rule.isDocument(): |
171 if not self.__networkExceptionTree.add(rule): |
172 if not self.__networkExceptionTree.add(rule): |
172 self.__networkBlockRules.append(rule) |
173 self.__networkBlockRules.append(rule) |
173 else: |
174 else: |
174 if not self.__networkBlockTree.add(rule): |
175 if not self.__networkBlockTree.add(rule): |
175 self.__networkBlockRules.append(rule) |
176 self.__networkBlockRules.append(rule) |
176 |
177 |
177 for rule in exceptionCssRules: |
178 for rule in exceptionCssRules: |
178 try: |
179 try: |
179 originalRule = cssRulesDict[rule.cssSelector()] |
180 originalRule = cssRulesDict[rule.cssSelector()] |
180 except KeyError: |
181 except KeyError: |
181 # If there is no such selector, the exception does nothing. |
182 # If there is no such selector, the exception does nothing. |
182 continue |
183 continue |
183 |
184 |
184 copiedRule = AdBlockRule() |
185 copiedRule = AdBlockRule() |
185 copiedRule.copyFrom(originalRule) |
186 copiedRule.copyFrom(originalRule) |
186 copiedRule.setOption(AdBlockRuleOption.DomainRestrictedOption) |
187 copiedRule.setOption(AdBlockRuleOption.DomainRestrictedOption) |
187 copiedRule.addBlockedDomains(rule.allowedDomains()) |
188 copiedRule.addBlockedDomains(rule.allowedDomains()) |
188 |
189 |
189 cssRulesDict[rule.cssSelector()] = copiedRule |
190 cssRulesDict[rule.cssSelector()] = copiedRule |
190 self.__createdRules.append(copiedRule) |
191 self.__createdRules.append(copiedRule) |
191 |
192 |
192 # Excessive amount of selectors for one CSS rule is not what the |
193 # Excessive amount of selectors for one CSS rule is not what the |
193 # rendering engine likes. So split them up by 1.000 selectors. |
194 # rendering engine likes. So split them up by 1.000 selectors. |
194 hidingRulesCount = 0 |
195 hidingRulesCount = 0 |
195 for key in cssRulesDict: |
196 for key in cssRulesDict: |
196 rule = cssRulesDict[key] |
197 rule = cssRulesDict[key] |
197 |
198 |
198 if rule.isDomainRestricted(): |
199 if rule.isDomainRestricted(): |
199 self.__domainRestrictedCssRules.append(rule) |
200 self.__domainRestrictedCssRules.append(rule) |
200 elif hidingRulesCount == 1000: |
201 elif hidingRulesCount == 1000: |
201 self.__elementHidingRules += rule.cssSelector() |
202 self.__elementHidingRules += rule.cssSelector() |
202 self.__elementHidingRules += "{display:none !important;} " |
203 self.__elementHidingRules += "{display:none !important;} " |
203 hidingRulesCount = 0 |
204 hidingRulesCount = 0 |
204 else: |
205 else: |
205 self.__elementHidingRules += rule.cssSelector() + "," |
206 self.__elementHidingRules += rule.cssSelector() + "," |
206 hidingRulesCount += 1 |
207 hidingRulesCount += 1 |
207 |
208 |
208 if hidingRulesCount != 0: |
209 if hidingRulesCount != 0: |
209 self.__elementHidingRules = self.__elementHidingRules[:-1] |
210 self.__elementHidingRules = self.__elementHidingRules[:-1] |
210 self.__elementHidingRules += "{display:none !important;} " |
211 self.__elementHidingRules += "{display:none !important;} " |
211 |
212 |
212 def clear(self): |
213 def clear(self): |
213 """ |
214 """ |
214 Public slot to clear the internal structures. |
215 Public slot to clear the internal structures. |
215 """ |
216 """ |
216 self.__createdRules = [] |
217 self.__createdRules = [] |
217 self.__networkExceptionRules = [] |
218 self.__networkExceptionRules = [] |
218 self.__networkBlockRules = [] |
219 self.__networkBlockRules = [] |
219 self.__domainRestrictedCssRules = [] |
220 self.__domainRestrictedCssRules = [] |
220 self.__documentRules = [] |
221 self.__documentRules = [] |
221 self.__elemhideRules = [] |
222 self.__elemhideRules = [] |
222 |
223 |
223 self.__elementHidingRules = "" |
224 self.__elementHidingRules = "" |
224 self.__networkBlockTree.clear() |
225 self.__networkBlockTree.clear() |
225 self.__networkExceptionTree.clear() |
226 self.__networkExceptionTree.clear() |