|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2012 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the manager for GreaseMonkey scripts. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 |
|
12 import os |
|
13 |
|
14 from PyQt5.QtCore import pyqtSignal, QObject, QTimer, QFile, QDir, QSettings, \ |
|
15 QUrl, QByteArray |
|
16 from PyQt5.QtNetwork import QNetworkAccessManager |
|
17 from PyQt5.QtWebKitWidgets import QWebFrame |
|
18 |
|
19 import Utilities |
|
20 import Preferences |
|
21 |
|
22 |
|
23 class GreaseMonkeyManager(QObject): |
|
24 """ |
|
25 Class implementing the manager for GreaseMonkey scripts. |
|
26 |
|
27 @signal scriptsChanged() emitted to indicate a change of scripts |
|
28 """ |
|
29 scriptsChanged = pyqtSignal() |
|
30 |
|
31 def __init__(self, parent=None): |
|
32 """ |
|
33 Constructor |
|
34 |
|
35 @param parent reference to the parent object (QObject) |
|
36 """ |
|
37 super(GreaseMonkeyManager, self).__init__(parent) |
|
38 |
|
39 self.__disabledScripts = [] |
|
40 self.__endScripts = [] |
|
41 self.__startScripts = [] |
|
42 self.__downloaders = [] |
|
43 |
|
44 QTimer.singleShot(0, self.__load) |
|
45 |
|
46 def showConfigurationDialog(self, parent=None): |
|
47 """ |
|
48 Public method to show the configuration dialog. |
|
49 |
|
50 @param parent reference to the parent widget (QWidget) |
|
51 """ |
|
52 from .GreaseMonkeyConfiguration.GreaseMonkeyConfigurationDialog \ |
|
53 import GreaseMonkeyConfigurationDialog |
|
54 self.__configDiaolg = GreaseMonkeyConfigurationDialog(self, parent) |
|
55 self.__configDiaolg.show() |
|
56 |
|
57 def downloadScript(self, request): |
|
58 """ |
|
59 Public method to download a GreaseMonkey script. |
|
60 |
|
61 @param request reference to the request (QNetworkRequest) |
|
62 """ |
|
63 from .GreaseMonkeyDownloader import GreaseMonkeyDownloader |
|
64 downloader = GreaseMonkeyDownloader(request, self) |
|
65 downloader.finished.connect( |
|
66 lambda: self.__downloaderFinished(downloader)) |
|
67 self.__downloaders.append(downloader) |
|
68 |
|
69 def __downloaderFinished(self, downloader): |
|
70 """ |
|
71 Private slot to handle the completion of a script download. |
|
72 |
|
73 @param downloader reference to the downloader object |
|
74 @type GreaseMonkeyDownloader |
|
75 """ |
|
76 if downloader in self.__downloaders: |
|
77 self.__downloaders.remove(downloader) |
|
78 |
|
79 def scriptsDirectory(self): |
|
80 """ |
|
81 Public method to get the path of the scripts directory. |
|
82 |
|
83 @return path of the scripts directory (string) |
|
84 """ |
|
85 return os.path.join( |
|
86 Utilities.getConfigDir(), "browser", "greasemonkey") |
|
87 |
|
88 def requireScriptsDirectory(self): |
|
89 """ |
|
90 Public method to get the path of the scripts directory. |
|
91 |
|
92 @return path of the scripts directory (string) |
|
93 """ |
|
94 return os.path.join(self.scriptsDirectory(), "requires") |
|
95 |
|
96 def requireScripts(self, urlList): |
|
97 """ |
|
98 Public method to get the sources of all required scripts. |
|
99 |
|
100 @param urlList list of URLs (list of string) |
|
101 @return sources of all required scripts (string) |
|
102 """ |
|
103 requiresDir = QDir(self.requireScriptsDirectory()) |
|
104 if not requiresDir.exists() or len(urlList) == 0: |
|
105 return "" |
|
106 |
|
107 script = "" |
|
108 |
|
109 settings = QSettings( |
|
110 os.path.join(self.requireScriptsDirectory(), "requires.ini"), |
|
111 QSettings.IniFormat) |
|
112 settings.beginGroup("Files") |
|
113 for url in urlList: |
|
114 if settings.contains(url): |
|
115 fileName = settings.value(url) |
|
116 try: |
|
117 f = open(fileName, "r", encoding="utf-8") |
|
118 source = f.read() |
|
119 f.close() |
|
120 except (IOError, OSError): |
|
121 source = "" |
|
122 script += source.strip() + "\n" |
|
123 |
|
124 return script |
|
125 |
|
126 def saveConfiguration(self): |
|
127 """ |
|
128 Public method to save the configuration. |
|
129 """ |
|
130 Preferences.setHelp("GreaseMonkeyDisabledScripts", |
|
131 self.__disabledScripts) |
|
132 |
|
133 def allScripts(self): |
|
134 """ |
|
135 Public method to get a list of all scripts. |
|
136 |
|
137 @return list of all scripts (list of GreaseMonkeyScript) |
|
138 """ |
|
139 return self.__startScripts[:] + self.__endScripts[:] |
|
140 |
|
141 def containsScript(self, fullName): |
|
142 """ |
|
143 Public method to check, if the given script exists. |
|
144 |
|
145 @param fullName full name of the script (string) |
|
146 @return flag indicating the existence (boolean) |
|
147 """ |
|
148 for script in self.__startScripts: |
|
149 if script.fullName() == fullName: |
|
150 return True |
|
151 for script in self.__endScripts: |
|
152 if script.fullName() == fullName: |
|
153 return True |
|
154 return False |
|
155 |
|
156 def enableScript(self, script): |
|
157 """ |
|
158 Public method to enable the given script. |
|
159 |
|
160 @param script script to be enabled (GreaseMonkeyScript) |
|
161 """ |
|
162 script.setEnabled(True) |
|
163 fullName = script.fullName() |
|
164 if fullName in self.__disabledScripts: |
|
165 self.__disabledScripts.remove(fullName) |
|
166 |
|
167 def disableScript(self, script): |
|
168 """ |
|
169 Public method to disable the given script. |
|
170 |
|
171 @param script script to be disabled (GreaseMonkeyScript) |
|
172 """ |
|
173 script.setEnabled(False) |
|
174 fullName = script.fullName() |
|
175 if fullName not in self.__disabledScripts: |
|
176 self.__disabledScripts.append(fullName) |
|
177 |
|
178 def addScript(self, script): |
|
179 """ |
|
180 Public method to add a script. |
|
181 |
|
182 @param script script to be added (GreaseMonkeyScript) |
|
183 @return flag indicating success (boolean) |
|
184 """ |
|
185 if not script: |
|
186 return False |
|
187 |
|
188 from .GreaseMonkeyScript import GreaseMonkeyScript |
|
189 if script.startAt() == GreaseMonkeyScript.DocumentStart: |
|
190 self.__startScripts.append(script) |
|
191 else: |
|
192 self.__endScripts.append(script) |
|
193 |
|
194 self.scriptsChanged.emit() |
|
195 return True |
|
196 |
|
197 def removeScript(self, script): |
|
198 """ |
|
199 Public method to remove a script. |
|
200 |
|
201 @param script script to be removed (GreaseMonkeyScript) |
|
202 @return flag indicating success (boolean) |
|
203 """ |
|
204 if not script: |
|
205 return False |
|
206 |
|
207 from .GreaseMonkeyScript import GreaseMonkeyScript |
|
208 if script.startAt() == GreaseMonkeyScript.DocumentStart: |
|
209 try: |
|
210 self.__startScripts.remove(script) |
|
211 except ValueError: |
|
212 pass |
|
213 else: |
|
214 try: |
|
215 self.__endScripts.remove(script) |
|
216 except ValueError: |
|
217 pass |
|
218 |
|
219 fullName = script.fullName() |
|
220 if fullName in self.__disabledScripts: |
|
221 self.__disabledScripts.remove(fullName) |
|
222 QFile.remove(script.fileName()) |
|
223 |
|
224 self.scriptsChanged.emit() |
|
225 return True |
|
226 |
|
227 def canRunOnScheme(self, scheme): |
|
228 """ |
|
229 Public method to check, if scripts can be run on a scheme. |
|
230 |
|
231 @param scheme scheme to check (string) |
|
232 @return flag indicating, that scripts can be run (boolean) |
|
233 """ |
|
234 return scheme in ["http", "https", "data", "ftp"] |
|
235 |
|
236 def pageLoadStarted(self): |
|
237 """ |
|
238 Public slot to handle the start of loading a page. |
|
239 """ |
|
240 frame = self.sender() |
|
241 if frame is None or not isinstance(frame, QWebFrame): |
|
242 return |
|
243 |
|
244 urlScheme = frame.url().scheme() |
|
245 urlString = bytes(frame.url().toEncoded()).decode() |
|
246 |
|
247 if not self.canRunOnScheme(urlScheme): |
|
248 return |
|
249 |
|
250 from .GreaseMonkeyJavaScript import bootstrap_js |
|
251 for script in self.__startScripts: |
|
252 if script.match(urlString): |
|
253 frame.evaluateJavaScript(bootstrap_js + script.script()) |
|
254 |
|
255 for script in self.__endScripts: |
|
256 if script.match(urlString): |
|
257 javascript = 'window.addEventListener("DOMContentLoaded",' \ |
|
258 'function(e) {{ {0} }}, false);'.format( |
|
259 bootstrap_js + script.script()) |
|
260 frame.evaluateJavaScript(javascript) |
|
261 |
|
262 def __load(self): |
|
263 """ |
|
264 Private slot to load the available scripts into the manager. |
|
265 """ |
|
266 scriptsDir = QDir(self.scriptsDirectory()) |
|
267 if not scriptsDir.exists(): |
|
268 scriptsDir.mkpath(self.scriptsDirectory()) |
|
269 |
|
270 if not scriptsDir.exists("requires"): |
|
271 scriptsDir.mkdir("requires") |
|
272 |
|
273 self.__disabledScripts = \ |
|
274 Preferences.getHelp("GreaseMonkeyDisabledScripts") |
|
275 |
|
276 from .GreaseMonkeyScript import GreaseMonkeyScript |
|
277 for fileName in scriptsDir.entryList(["*.js"], QDir.Files): |
|
278 absolutePath = scriptsDir.absoluteFilePath(fileName) |
|
279 script = GreaseMonkeyScript(self, absolutePath) |
|
280 |
|
281 if script.fullName() in self.__disabledScripts: |
|
282 script.setEnabled(False) |
|
283 |
|
284 if script.startAt() == GreaseMonkeyScript.DocumentStart: |
|
285 self.__startScripts.append(script) |
|
286 else: |
|
287 self.__endScripts.append(script) |
|
288 |
|
289 def connectPage(self, page): |
|
290 """ |
|
291 Public method to allow the GreaseMonkey manager to connect to the page. |
|
292 |
|
293 @param page reference to the web page (HelpWebPage) |
|
294 """ |
|
295 page.mainFrame().javaScriptWindowObjectCleared.connect( |
|
296 self.pageLoadStarted) |
|
297 |
|
298 def createRequest(self, op, request, outgoingData=None): |
|
299 """ |
|
300 Public method to create a request. |
|
301 |
|
302 @param op the operation to be performed |
|
303 (QNetworkAccessManager.Operation) |
|
304 @param request reference to the request object (QNetworkRequest) |
|
305 @param outgoingData reference to an IODevice containing data to be sent |
|
306 (QIODevice) |
|
307 @return reference to the created reply object (QNetworkReply) |
|
308 """ |
|
309 if op == QNetworkAccessManager.GetOperation and \ |
|
310 request.rawHeader(b"X-Eric6-UserLoadAction") == QByteArray(b"1"): |
|
311 urlString = request.url().toString( |
|
312 QUrl.RemoveFragment | QUrl.RemoveQuery) |
|
313 if urlString.endswith(".user.js"): |
|
314 self.downloadScript(request) |
|
315 from Helpviewer.Network.EmptyNetworkReply import \ |
|
316 EmptyNetworkReply |
|
317 return EmptyNetworkReply(self) |
|
318 |
|
319 return None |