--- a/WebBrowser/AdBlock/AdBlockManager.py Thu Dec 14 19:25:34 2017 +0100 +++ b/WebBrowser/AdBlock/AdBlockManager.py Mon Dec 18 18:09:39 2017 +0100 @@ -12,13 +12,14 @@ import os from PyQt5.QtCore import pyqtSignal, QObject, QUrl, QUrlQuery, QFile, \ - QByteArray + QByteArray, QMutex, QMutexLocker from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInfo from E5Gui import E5MessageBox from .AdBlockSubscription import AdBlockSubscription from .AdBlockUrlInterceptor import AdBlockUrlInterceptor +from .AdBlockMatcher import AdBlockMatcher from Utilities.AutoSaver import AutoSaver import Utilities @@ -32,15 +33,19 @@ @signal rulesChanged() emitted after some rule has changed @signal requiredSubscriptionLoaded(subscription) emitted to indicate loading of a required subscription is finished (AdBlockSubscription) + @signal enabledChanged(enabled) emitted to indicate a change of the + enabled state """ rulesChanged = pyqtSignal() requiredSubscriptionLoaded = pyqtSignal(AdBlockSubscription) + enabledChanged = pyqtSignal(bool) def __init__(self, parent=None): """ Constructor - @param parent reference to the parent object (QObject) + @param parent reference to the parent object + @type QObject """ super(AdBlockManager, self).__init__(parent) @@ -64,6 +69,9 @@ self.__customSubscriptionUrlString = \ bytes(self.__customSubscriptionUrl().toEncoded()).decode() + self.__mutex = QMutex() + self.__matcher = AdBlockMatcher(self) + self.rulesChanged.connect(self.__saveTimer.changeOccurred) self.rulesChanged.connect(self.__rulesChanged) @@ -79,6 +87,7 @@ """ from WebBrowser.WebBrowserWindow import WebBrowserWindow WebBrowserWindow.mainWindow().reloadUserStyleSheet() + self.__updateMatcher() def close(self): """ @@ -94,7 +103,8 @@ """ Public method to check, if blocking ads is enabled. - @return flag indicating the enabled state (boolean) + @return flag indicating the enabled state + @rtype bool """ if not self.__loaded: self.load() @@ -105,7 +115,8 @@ """ Public slot to set the enabled state. - @param enabled flag indicating the enabled state (boolean) + @param enabled flag indicating the enabled state + @type bool """ if self.isEnabled() == enabled: return @@ -116,60 +127,59 @@ mainWindow.adBlockIcon().setEnabled(enabled) if enabled: self.__loadSubscriptions() + self.rulesChanged.emit() + self.enabledChanged.emit(enabled) def block(self, info): """ Public method to check, if a request should be blocked. - @param info request info aobject + @param info request info object @type QWebEngineUrlRequestInfo @return flag indicating to block the request @rtype bool """ + locker = QMutexLocker(self.__mutex) # __IGNORE_WARNING__ + + if not self.isEnabled(): + return False + urlString = bytes(info.requestUrl().toEncoded()).decode().lower() urlDomain = info.requestUrl().host().lower() urlScheme = info.requestUrl().scheme().lower() - refererHost = info.firstPartyUrl().host().lower() - if not self.isEnabled() or not self.__canRunOnScheme(urlScheme): + if not self.canRunOnScheme(urlScheme) or \ + not self.__canBeBlocked(info.firstPartyUrl()): return False - if self.isHostExcepted(urlDomain) or self.isHostExcepted(refererHost): - return False - res = False + blockedRule = self.__matcher.match(info, urlDomain, urlString) - for subscription in self.subscriptions(): - if subscription.isEnabled(): - if subscription.adBlockDisabledForUrl(info.requestUrl()): - continue - - blockedRule = subscription.match(info, urlDomain, urlString) - if blockedRule: - res = True - if info.resourceType() == \ - QWebEngineUrlRequestInfo.ResourceTypeMainFrame: - url = QUrl("eric:adblock") - query = QUrlQuery() - query.addQueryItem("rule", blockedRule.filter()) - query.addQueryItem( - "subscription", blockedRule.subscription().title()) - url.setQuery(query) - info.redirect(url) - res = False - else: - info.block(True) - break + if blockedRule: + res = True + if info.resourceType() == \ + QWebEngineUrlRequestInfo.ResourceTypeMainFrame: + url = QUrl("eric:adblock") + query = QUrlQuery() + query.addQueryItem("rule", blockedRule.filter()) + query.addQueryItem( + "subscription", blockedRule.subscription().title()) + url.setQuery(query) + info.redirect(url) + else: + info.block(True) return res - def __canRunOnScheme(self, scheme): + def canRunOnScheme(self, scheme): """ - Private method to check, if AdBlock can be performed on the scheme. + Public method to check, if AdBlock can be performed on the scheme. - @param scheme scheme to check (string) - @return flag indicating, that AdBlock can be performed (boolean) + @param scheme scheme to check + @type str + @return flag indicating, that AdBlock can be performed + @rtype bool """ return scheme not in ["data", "eric", "qthelp", "qrc", "file", "abp"] @@ -177,7 +187,8 @@ """ Public method to get a reference to the page block object. - @return reference to the page block object (AdBlockPage) + @return reference to the page block object + @rtype AdBlockPage """ if self.__adBlockPage is None: from .AdBlockPage import AdBlockPage @@ -188,7 +199,8 @@ """ Private method to generate the path for custom subscriptions. - @return URL for custom subscriptions (QUrl) + @return URL for custom subscriptions + @rtype QUrl """ dataDir = os.path.join(Utilities.getConfigDir(), "web_browser", "subscriptions") @@ -201,7 +213,8 @@ """ Private method to generate the URL for custom subscriptions. - @return URL for custom subscriptions (QUrl) + @return URL for custom subscriptions + @rtype QUrl """ location = self.__customSubscriptionLocation() encodedUrl = bytes(location.toEncoded()).decode() @@ -213,7 +226,8 @@ """ Public method to get a subscription for custom rules. - @return subscription object for custom rules (AdBlockSubscription) + @return subscription object for custom rules + @rtype AdBlockSubscription """ location = self.__customSubscriptionLocation() for subscription in self.__subscriptions: @@ -229,7 +243,8 @@ """ Public method to get all subscriptions. - @return list of subscriptions (list of AdBlockSubscription) + @return list of subscriptions + @rtype list of AdBlockSubscription """ if not self.__loaded: self.load() @@ -240,8 +255,10 @@ """ Public method to get a subscription based on its location. - @param location location of the subscription to search for (string) - @return subscription or None (AdBlockSubscription) + @param location location of the subscription to search for + @type str + @return subscription or None + @rtype AdBlockSubscription """ if location != "": for subscription in self.__subscriptions: @@ -262,8 +279,9 @@ Public method to remove an AdBlock subscription. @param subscription AdBlock subscription to be removed - (AdBlockSubscription) - @param emitSignal flag indicating to send a signal (boolean) + @type AdBlockSubscription + @param emitSignal flag indicating to send a signal + @type bool """ if subscription is None: return @@ -320,13 +338,15 @@ dlg.addSubscription(subscription, False) dlg.setFocus() dlg.raise_() + + return res def addSubscription(self, subscription): """ Public method to add an AdBlock subscription. @param subscription AdBlock subscription to be added - (AdBlockSubscription) + @type AdBlockSubscription """ if subscription is None: return @@ -410,16 +430,24 @@ adBlockSubscription.rulesChanged.connect(self.rulesChanged) adBlockSubscription.changed.connect(self.rulesChanged) adBlockSubscription.enabledChanged.connect(self.rulesChanged) + adBlockSubscription.rulesEnabledChanged.connect( + self.__updateMatcher) + adBlockSubscription.rulesEnabledChanged.connect( + self.__saveTimer.changeOccurred) self.__subscriptions.append(adBlockSubscription) self.__subscriptionsLoaded = True + + self.__updateMatcher() def loadRequiredSubscription(self, location, title): """ Public method to load a subscription required by another one. - @param location location of the required subscription (string) - @param title title of the required subscription (string) + @param location location of the required subscription + @type str + @param title title of the required subscription + @type str """ # Step 1: check, if the subscription is in the list of subscriptions urlString = "abp:subscribe?location={0}&title={1}".format( @@ -440,9 +468,10 @@ Public method to get a list of subscriptions, that require the given one. - @param subscription subscription to check for (AdBlockSubscription) - @return list of subscription requiring the given one (list of - AdBlockSubscription) + @param subscription subscription to check for + @type AdBlockSubscription + @return list of subscription requiring the given one + @rtype list of AdBlockSubscription """ subscriptions = [] location = subscription.location().toString() @@ -456,7 +485,8 @@ """ Public slot to show the AdBlock subscription management dialog. - @return reference to the dialog (AdBlockDialog) + @return reference to the dialog + @rtype AdBlockDialog """ if self.__adBlockDialog is None: from .AdBlockDialog import AdBlockDialog @@ -470,54 +500,40 @@ Public method to get the element hiding rules. - @param url URL to get hiding rules for (QUrl) - @return element hiding rules (string) + @param url URL to get hiding rules for + @type QUrl + @return element hiding rules + @rtype str """ - if not self.isEnabled() or not self.__canRunOnScheme(url.scheme()): + if not self.isEnabled() or \ + not self.canRunOnScheme(url.scheme()) or \ + not self.__canBeBlocked(url): return "" - rules = "" - - for subscription in self.__subscriptions: - rules += subscription.elementHidingRules() - - if rules: - # remove last ", - rules = rules[:-1] - - return rules + return self.__matcher.elementHidingRules() def elementHidingRulesForDomain(self, url): """ Public method to get the element hiding rules for a domain. - @param url URL to get hiding rules for (QUrl) - @return element hiding rules (string) + @param url URL to get hiding rules for + @type QUrl + @return element hiding rules + @rtype str """ - if not self.isEnabled(): + if not self.isEnabled() or \ + not self.canRunOnScheme(url.scheme()) or \ + not self.__canBeBlocked(url): return "" - rules = "" - - for subscription in self.__subscriptions: - if subscription.elemHideDisabledForUrl(url): - continue - - rules += subscription.elementHidingRulesForDomain(url.host()) - - if rules: - # remove last "," - rules = rules[:-1] - - rules += "{display:none !important;}\n" - - return rules + return self.__matcher.elementHidingRulesForDomain(url.host()) def exceptions(self): """ Public method to get a list of excepted hosts. - @return list of excepted hosts (list of string) + @return list of excepted hosts + @rtype list of str """ return self.__exceptedHosts @@ -525,7 +541,8 @@ """ Public method to set the list of excepted hosts. - @param hosts list of excepted hosts (list of string) + @param hosts list of excepted hosts + @type list of str """ self.__exceptedHosts = [host.lower() for host in hosts] Preferences.setWebBrowser("AdBlockExceptions", self.__exceptedHosts) @@ -534,7 +551,8 @@ """ Public method to add an exception. - @param host to be excepted (string) + @param host to be excepted + @type str """ host = host.lower() if host and host not in self.__exceptedHosts: @@ -546,7 +564,8 @@ """ Public method to remove an exception. - @param host to be removed from the list of exceptions (string) + @param host to be removed from the list of exceptions + @type str """ host = host.lower() if host in self.__exceptedHosts: @@ -558,8 +577,10 @@ """ Public slot to check, if a host is excepted. - @param host host to check (string) - @return flag indicating an exception (boolean) + @param host host to check + @type str + @return flag indicating an exception + @rtype bool """ host = host.lower() return host in self.__exceptedHosts @@ -568,7 +589,8 @@ """ Public method to show the AdBlock Exceptions dialog. - @return reference to the exceptions dialog (AdBlockExceptionsDialog) + @return reference to the exceptions dialog + @rtype AdBlockExceptionsDialog """ if self.__adBlockExceptionsDialog is None: from .AdBlockExceptionsDialog import AdBlockExceptionsDialog @@ -611,3 +633,24 @@ @rtype str """ return self.__defaultSubscriptionUrlString + + def __updateMatcher(self): + """ + Private slot to update the adblock matcher. + """ + if self.__enabled: + self.__matcher.update() + else: + self.__matcher.clear() + + def __canBeBlocked(self, url): + """ + Private method to check, if the given URL could be blocked (i.e. is + not whitelisted). + + @param url URL to be checked + @type QUrl + @return flag indicating that the given URL can be blocked + @rtype bool + """ + return not self.__matcher.adBlockDisabledForUrl(url)