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