WebBrowser/AdBlock/AdBlockManager.py

changeset 6028
859f6894eed9
parent 5726
e1dbd217214a
child 6048
82ad8ec9548c
--- 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)

eric ide

mercurial