Implemented support for GreaseMonkey 4.0 for the web browser NG.

Sat, 10 Feb 2018 19:53:07 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 10 Feb 2018 19:53:07 +0100
changeset 6128
afc2cda1a743
parent 6127
128d9567a533
child 6129
049ab6ad3144

Implemented support for GreaseMonkey 4.0 for the web browser NG.

WebBrowser/GreaseMonkey/GreaseMonkeyJavaScript.py file | annotate | diff | comparison | revisions
WebBrowser/GreaseMonkey/GreaseMonkeyJsObject.py file | annotate | diff | comparison | revisions
WebBrowser/GreaseMonkey/GreaseMonkeyManager.py file | annotate | diff | comparison | revisions
eric6.e4p file | annotate | diff | comparison | revisions
--- a/WebBrowser/GreaseMonkey/GreaseMonkeyJavaScript.py	Sat Feb 10 17:24:42 2018 +0100
+++ b/WebBrowser/GreaseMonkey/GreaseMonkeyJavaScript.py	Sat Feb 10 19:53:07 2018 +0100
@@ -10,119 +10,110 @@
 from __future__ import unicode_literals
 
 bootstrap_js = """
-if(typeof GM_xmlhttpRequest === "undefined") {
-    GM_xmlhttpRequest = function(/* object */ details) {
-        details.method = details.method.toUpperCase() || "GET";
+var GM = {
+    info: {
+        script: {
+            description: "",
+            excludes: [],
+            includes: [],
+            matches: [],
+            name: "",
+            namespace: "",
+            resources: {},
+            'run-at': "document-end",
+            version: ""
+        },
+        scriptMetaStr: "",
+        scriptHandler: "eric Browser GreaseMonkey",
+        version: "4.0"
+    }
+};
+window.GM = GM;
 
-        if(!details.url) {
-            throw("GM_xmlhttpRequest requires an URL.");
-        }
+function GM_info() {
+    return GM.info;
+}
+
+function GM_xmlhttpRequest(/* object */ details) {
+    details.method = details.method.toUpperCase() || "GET";
+
+    if (!details.url) {
+        throw("GM_xmlhttpRequest requires an URL.");
+    }
 
-        // build XMLHttpRequest object
-        var oXhr = new XMLHttpRequest;
-        // run it
-        if(oXhr) {
-            if("onreadystatechange" in details)
-                oXhr.onreadystatechange = function() {
-                    details.onreadystatechange(oXhr)
-                };
-            if("onload" in details)
-                oXhr.onload = function() { details.onload(oXhr) };
-            if("onerror" in details)
-                oXhr.onerror = function() { details.onerror(oXhr) };
+    // build XMLHttpRequest object
+    var oXhr = new XMLHttpRequest;
+    // run it
+    if("onreadystatechange" in details)
+        oXhr.onreadystatechange = function() {
+            details.onreadystatechange(oXhr) };
+    if("onload" in details)
+        oXhr.onload = function() { details.onload(oXhr) };
+    if("onerror" in details)
+        oXhr.onerror = function() { details.onerror(oXhr) };
+
+    oXhr.open(details.method, details.url, true);
+
+    if("headers" in details)
+        for(var header in details.headers)
+            oXhr.setRequestHeader(header, details.headers[header]);
 
-            oXhr.open(details.method, details.url, true);
-
-            if("headers" in details)
-                for(var header in details.headers)
-                    oXhr.setRequestHeader(header, details.headers[header]);
+    if("data" in details)
+        oXhr.send(details.data);
+    else
+        oXhr.send();
+}
 
-            if("data" in details)
-                oXhr.send(details.data);
-            else
-                oXhr.send();
-        } else
-            throw ("This Browser is not supported, please upgrade.")
+function GM_addStyle(/* string */ styles) {
+    var head = document.getElementsByTagName("head")[0];
+    if (head === undefined) {
+        document.onreadystatechange = function() {
+            if (document.readyState == "interactive") {
+                var oStyle = document.createElement("style");
+                oStyle.setAttribute("type", "text/css");
+                oStyle.appendChild(document.createTextNode(styles));
+                document.getElementsByTagName("head")[0].appendChild(oStyle);
+            }
+        }
+    }
+    else {
+        var oStyle = document.createElement("style");
+        oStyle.setAttribute("type", "text/css");
+        oStyle.appendChild(document.createTextNode(styles));
+        head.appendChild(oStyle);
     }
 }
 
-if(typeof GM_addStyle === "undefined") {
-    function GM_addStyle(/* String */ styles) {
-        var head = document.getElementsByTagName("head")[0];
-        if (head === undefined) {
-            document.onreadystatechange = function() {
-                if (document.readyState == "interactive") {
-                  var oStyle = document.createElement("style");
-                  oStyle.setAttribute("type", "text\/css");
-                  oStyle.appendChild(document.createTextNode(styles));
-                  document.getElementsByTagName("head")[0].appendChild(oStyle);
-                }
-            }
-        }
-        else {
-            var oStyle = document.createElement("style");
-            oStyle.setAttribute("type", "text\/css");
-            oStyle.appendChild(document.createTextNode(styles));
-            head.appendChild(oStyle);
-        }
-    }
+function GM_log(log) {
+    if(console)
+        console.log(log);
 }
 
-if(typeof GM_log === "undefined") {
-    function GM_log(log) {
-        if(console)
-            console.log(log);
-    }
+function GM_openInTab(url) {
+    return window.open(url);
 }
 
-if(typeof GM_openInTab === "undefined") {
-    function GM_openInTab(url) {
-        window.open(url)
-    }
+function GM_setClipboard(text) {
+    external.extra.greasemonkey.setClipboard(text);
 }
 
-// Define unsafe window
-var unsafeWindow = window;
-window.wrappedJSObject = unsafeWindow;
+// GM_registerMenuCommand not supported
+function GM_registerMenuCommand(caption, commandFunc, accessKey) { }
 
-// GM_registerMenuCommand not supported
-if(typeof GM_registerMenuCommand === "undefined") {
-    function GM_registerMenuCommand(caption, commandFunc, accessKey) { }
-}
-
-// GM Resource not supported
-if(typeof GM_getResourceText === "undefined") {
-    function GM_getResourceText(resourceName) {
-        throw ("eric6 Web Browser: GM Resource is not supported!");
-    }
-}
+// GM_getResourceUrl not supported
+function GM_getResourceUrl(resourceName) { }
 
-if(typeof GM_getResourceURL === "undefined") {
-    function GM_getResourceURL(resourceName) {
-        throw ("eric6 Web Browser: GM Resource is not supported!");
-    }
-}
-
-// GM Settings not supported
-if(typeof GM_getValue === "undefined") {
-    function GM_getValue(name, defaultValue) {
-        return defaultValue;
-    }
-}
+// GreaseMonkey 4.0 support
+GM.openInTab = GM_openInTab;
+GM.setClipboard = GM_setClipboard;
+GM.xmlhttpRequest = GM_xmlhttpRequest;
 
-if(typeof GM_setValue === "undefined") {
-    function GM_setValue(name, value) { }
-}
-
-if(typeof GM_deleteValue === "undefined") {
-    function GM_deleteValue(name) { }
-}
-
-if(typeof GM_listValues === "undefined") {
-    function GM_listValues() {
-        return new Array("");
-    }
-}
+// GM_getResourceUrl not supported
+GM.getResourceUrl = function(resourceName) {
+    return new Promise((resolve, reject) => {
+        reject();
+    });
+};
 """
 
 
@@ -152,4 +143,90 @@
 function GM_setValue(aKey, aVal) {{
     localStorage.setItem("{0}" + aKey, aVal);
 }}
+
+// GreaseMonkey 4.0 support
+var asyncCall = (func) => {{
+    if (window._eric_external) {{
+        func();
+    }} else {{
+        document.addEventListener("_eric_external_created", func);
+    }}
+}};
+
+var decode = (val) => {{
+    val = String(val);
+    if (!val.length) {{
+        return val;
+    }}
+    var v = val.substr(1);
+    if (val[0] == "b") {{
+        return Boolean(v == "true" ? true : false);
+    }} else if (val[0] == "i") {{
+        return Number(v);
+    }} else if (val[0] == "s") {{
+        return v;
+    }} else {{
+        return undefined;
+    }}
+}};
+
+var encode = (val) => {{
+    if (typeof val == "boolean") {{
+        return "b" + (val ? "true" : "false");
+    }} else if (typeof val == "number") {{
+        return "i" + String(val);
+    }} else if (typeof val == "string") {{
+        return "s" + val;
+    }} else {{
+        return "";
+    }}
+}};
+
+GM.deleteValue = function(name) {{
+    return new Promise((resolve, reject) => {{
+        asyncCall(() => {{
+            external.extra.greasemonkey.deleteValue("{0}", name, (res) => {{
+                if (res) {{
+                    resolve();
+                }} else {{
+                    reject();
+                }}
+            }});
+        }});
+    }});
+}};
+
+GM.getValue = function(name, value) {{
+    return new Promise((resolve) => {{
+        asyncCall(() => {{
+            external.extra.greasemonkey.getValue("{0}", name, encode(value),
+                                                 (res) => {{
+                resolve(decode(res));
+            }});
+        }});
+    }});
+}};
+
+GM.setValue = function(name, value) {{
+    return new Promise((resolve, reject) => {{
+        asyncCall(() => {{
+            external.extra.greasemonkey.setValue("{0}", name, encode(value),
+                                                 (res) => {{
+                if (res) {{
+                    resolve();
+                }} else {{
+                    reject();
+                }}
+            }});
+        }});
+    }});
+}};
+
+GM.listValues = function() {{
+    return new Promise((resolve) => {{
+        asyncCall(() => {{
+            external.extra.greasemonkey.listValues("{0}", resolve);
+        }});
+    }});
+}};
 """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/GreaseMonkey/GreaseMonkeyJsObject.py	Sat Feb 10 19:53:07 2018 +0100
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 - 2018 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the Python side for GreaseMonkey scripts.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import pyqtSlot, QObject, QSettings
+from PyQt5.QtGui import QGuiApplication
+
+
+class GreaseMonkeyJsObject(QObject):
+    """
+    Class implementing the Python side for GreaseMonkey scripts.
+    """
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent object
+        @type QObject
+        """
+        super(GreaseMonkeyJsObject, self).__init__(parent)
+        
+        self.__settings = None
+    
+    def setSettingsFile(self, name):
+        """
+        Public method to set the settings file for the GreaseMonkey parameters.
+        
+        @param name name of the settings file
+        @type str
+        """
+        if self.__settings is not None:
+            self.__settings.sync()
+            self.__settings = None
+        
+        self.__settings = QSettings(name, QSettings.IniFormat)
+    
+    @pyqtSlot(str, str, str)
+    def getValue(self, nspace, name, dValue):
+        """
+        Public slot to get the value for the named variable for the identified
+        script.
+        
+        @param nspace unique script id
+        @type str
+        @param name name of the variable
+        @type str
+        @param dValue default value
+        @type str
+        @return value for the named variable
+        @rtype str
+        """
+        vName = "GreaseMonkey-{0}/{1}".format(nspace, name)
+        sValue = self.__settings.value(vName, dValue)
+        if not sValue:
+            return dValue
+        
+        return sValue
+    
+    @pyqtSlot(str, str, str)
+    def setValue(self, nspace, name, value):
+        """
+        Public slot to set the value for the named variable for the identified
+        script.
+        
+        @param nspace unique script id
+        @type str
+        @param name name of the variable
+        @type str
+        @param value value to be set
+        @type str
+        @return flag indicating success
+        @rtype bool
+        """
+        vName = "GreaseMonkey-{0}/{1}".format(nspace, name)
+        self.__settings.setValue(vName, value)
+        self.__settings.sync()
+        return True
+    
+    @pyqtSlot(str, str)
+    def deleteValue(self, nspace, name):
+        """
+        Public slot to set delete the named variable for the identified script.
+        
+        @param nspace unique script id
+        @type str
+        @param name name of the variable
+        @type str
+        @return flag indicating success
+        @rtype bool
+        """
+        vName = "GreaseMonkey-{0}/{1}".format(nspace, name)
+        self.__settings.remove(vName)
+        self.__settings.sync()
+        return True
+    
+    @pyqtSlot(str)
+    def listValues(self, nspace):
+        """
+        Public slot to list the stored variables for the identified script.
+        
+        @param nspace unique script id
+        @type str
+        @return list of stored variables
+        @rtype list of str
+        """
+        nspaceName = "GreaseMonkey-{0}".format(nspace)
+        self.__settings.beginGroup(nspaceName)
+        keys = self.__settings.allKeys()
+        self.__settings.endGroup()
+        
+        return keys
+    
+    @pyqtSlot(str)
+    def setClipboard(self, text):
+        """
+        Public slot to set some clipboard text.
+        
+        @param text text to be copied to the clipboard
+        @type str
+        """
+        QGuiApplication.clipboard().setText(text)
--- a/WebBrowser/GreaseMonkey/GreaseMonkeyManager.py	Sat Feb 10 17:24:42 2018 +0100
+++ b/WebBrowser/GreaseMonkey/GreaseMonkeyManager.py	Sat Feb 10 19:53:07 2018 +0100
@@ -21,6 +21,9 @@
 import Preferences
 
 from WebBrowser.WebBrowserWindow import WebBrowserWindow
+from WebBrowser.JavaScript.ExternalJsObject import ExternalJsObject
+
+from .GreaseMonkeyJsObject import GreaseMonkeyJsObject
 
 
 class GreaseMonkeyManager(QObject):
@@ -43,6 +46,8 @@
         self.__scripts = []
         self.__downloaders = []
         
+        self.__jsObject = GreaseMonkeyJsObject(self)
+        
         QTimer.singleShot(0, self.__load)
     
     def showConfigurationDialog(self, parent=None):
@@ -322,6 +327,11 @@
             else:
                 collection = WebBrowserWindow.webProfile().scripts()
                 collection.insert(script.webScript())
+        
+        self.__jsObject.setSettingsFile(os.path.join(
+            Utilities.getConfigDir(), "web_browser",
+            "greasemonkey_values.ini"))
+        ExternalJsObject.registerExtraObject("GreaseMonkey", self.__jsObject)
     
     def __scriptChanged(self, script):
         """
--- a/eric6.e4p	Sat Feb 10 17:24:42 2018 +0100
+++ b/eric6.e4p	Sat Feb 10 19:53:07 2018 +0100
@@ -1472,6 +1472,7 @@
     <Source>WebBrowser/GreaseMonkey/GreaseMonkeyConfiguration/__init__.py</Source>
     <Source>WebBrowser/GreaseMonkey/GreaseMonkeyDownloader.py</Source>
     <Source>WebBrowser/GreaseMonkey/GreaseMonkeyJavaScript.py</Source>
+    <Source>WebBrowser/GreaseMonkey/GreaseMonkeyJsObject.py</Source>
     <Source>WebBrowser/GreaseMonkey/GreaseMonkeyManager.py</Source>
     <Source>WebBrowser/GreaseMonkey/GreaseMonkeyScript.py</Source>
     <Source>WebBrowser/GreaseMonkey/__init__.py</Source>
@@ -2199,14 +2200,14 @@
   </Resources>
   <Others>
     <Other>.hgignore</Other>
+    <Other>APIs/Python/zope-2.10.7.api</Other>
+    <Other>APIs/Python/zope-2.11.2.api</Other>
+    <Other>APIs/Python/zope-3.3.1.api</Other>
     <Other>APIs/Python3/PyQt4.bas</Other>
     <Other>APIs/Python3/PyQt5.bas</Other>
     <Other>APIs/Python3/QScintilla2.bas</Other>
     <Other>APIs/Python3/eric6.api</Other>
     <Other>APIs/Python3/eric6.bas</Other>
-    <Other>APIs/Python/zope-2.10.7.api</Other>
-    <Other>APIs/Python/zope-2.11.2.api</Other>
-    <Other>APIs/Python/zope-3.3.1.api</Other>
     <Other>APIs/QSS/qss.api</Other>
     <Other>APIs/Ruby/Ruby-1.8.7.api</Other>
     <Other>APIs/Ruby/Ruby-1.8.7.bas</Other>

eric ide

mercurial