WebBrowser/GreaseMonkey/GreaseMonkeyScript.py

changeset 5715
cbcca230679f
parent 5650
4c52f07c186e
child 5726
e1dbd217214a
equal deleted inserted replaced
5714:90c57b50600f 5715:cbcca230679f
8 """ 8 """
9 9
10 from __future__ import unicode_literals 10 from __future__ import unicode_literals
11 11
12 from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl, QRegExp, \ 12 from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl, QRegExp, \
13 QByteArray, QCryptographicHash 13 QByteArray, QCryptographicHash, qVersion
14 from PyQt5.QtWebEngineWidgets import QWebEngineScript 14 from PyQt5.QtWebEngineWidgets import QWebEngineScript
15 15
16 from .GreaseMonkeyJavaScript import bootstrap_js, values_js 16 from .GreaseMonkeyJavaScript import bootstrap_js, values_js
17 from .GreaseMonkeyDownloader import GreaseMonkeyDownloader
17 18
18 from ..Tools.DelayedFileWatcher import DelayedFileWatcher 19 from ..Tools.DelayedFileWatcher import DelayedFileWatcher
19 from ..WebBrowserPage import WebBrowserPage 20 from ..WebBrowserPage import WebBrowserPage
20 21
21 22
26 DocumentStart = 0 27 DocumentStart = 0
27 DocumentEnd = 1 28 DocumentEnd = 1
28 DocumentIdle = 2 29 DocumentIdle = 2
29 30
30 scriptChanged = pyqtSignal() 31 scriptChanged = pyqtSignal()
32 updatingChanged = pyqtSignal(bool)
31 33
32 def __init__(self, manager, path): 34 def __init__(self, manager, path):
33 """ 35 """
34 Constructor 36 Constructor
35 37
46 self.__description = "" 48 self.__description = ""
47 self.__version = "" 49 self.__version = ""
48 50
49 self.__include = [] 51 self.__include = []
50 self.__exclude = [] 52 self.__exclude = []
53 self.__require = []
51 54
52 self.__downloadUrl = QUrl() 55 self.__downloadUrl = QUrl()
53 self.__updateUrl = QUrl() 56 self.__updateUrl = QUrl()
54 self.__startAt = GreaseMonkeyScript.DocumentEnd 57 self.__startAt = GreaseMonkeyScript.DocumentEnd
55 58
57 self.__fileName = path 60 self.__fileName = path
58 self.__enabled = True 61 self.__enabled = True
59 self.__valid = False 62 self.__valid = False
60 self.__noFrames = False 63 self.__noFrames = False
61 64
65 self.__updating = False
66
67 self.__downloaders = []
68
62 self.__parseScript() 69 self.__parseScript()
63 70
64 self.__fileWatcher.delayedFileChanged.connect( 71 self.__fileWatcher.delayedFileChanged.connect(
65 self.__watchedFileChanged) 72 self.__watchedFileChanged)
66 73
175 182
176 @return list of excluded URLs (list of strings) 183 @return list of excluded URLs (list of strings)
177 """ 184 """
178 return self.__exclude[:] 185 return self.__exclude[:]
179 186
180 def script(self): 187 def require(self):
181 """ 188 """
182 Public method to get the Javascript source. 189 Public method to get the list of required scripts.
183 190
184 @return Javascript source (string) 191 @return list of required scripts (list of strings)
185 """ 192 """
186 return self.__script 193 return self.__require[:]
187 194
188 def fileName(self): 195 def fileName(self):
189 """ 196 """
190 Public method to get the path of the Javascript file. 197 Public method to get the path of the Javascript file.
191 198
192 @return path path of the Javascript file (string) 199 @return path of the Javascript file (string)
193 """ 200 """
194 return self.__fileName 201 return self.__fileName
202
203 def isUpdating(self):
204 """
205 Public method to get the updating flag.
206
207 @return updating flag
208 @rtype bool
209 """
210 return self.__updating
195 211
196 @pyqtSlot(str) 212 @pyqtSlot(str)
197 def __watchedFileChanged(self, fileName): 213 def __watchedFileChanged(self, fileName):
198 """ 214 """
199 Private slot handling changes of the script file. 215 Private slot handling changes of the script file.
200 216
201 @param fileName path of the script file 217 @param fileName path of the script file
202 @type str 218 @type str
203 """ 219 """
204 if self.__fileName == fileName: 220 if self.__fileName == fileName:
205 self.__parseScript() 221 self.__reloadScript()
206
207 self.__manager.removeScript(self, False)
208 self.__manager.addScript(self)
209
210 self.scriptChanged.emit()
211 222
212 def __parseScript(self): 223 def __parseScript(self):
213 """ 224 """
214 Private method to parse the given script and populate the data 225 Private method to parse the given script and populate the data
215 structure. 226 structure.
219 self.__description = "" 230 self.__description = ""
220 self.__version = "" 231 self.__version = ""
221 232
222 self.__include = [] 233 self.__include = []
223 self.__exclude = [] 234 self.__exclude = []
235 self.__require = []
224 236
225 self.__downloadUrl = QUrl() 237 self.__downloadUrl = QUrl()
226 self.__updateUrl = QUrl() 238 self.__updateUrl = QUrl()
227 self.__startAt = GreaseMonkeyScript.DocumentEnd 239 self.__startAt = GreaseMonkeyScript.DocumentEnd
228 240
248 260
249 if metaDataBlock == "": 261 if metaDataBlock == "":
250 # invalid script file 262 # invalid script file
251 return 263 return
252 264
253 requireList = []
254 for line in metaDataBlock.splitlines(): 265 for line in metaDataBlock.splitlines():
255 if not line.strip(): 266 if not line.strip():
256 continue 267 continue
257 268
258 if not line.startswith("// @"): 269 if not line.startswith("// @"):
288 299
289 elif key in ["@exclude", "@exclude_match"]: 300 elif key in ["@exclude", "@exclude_match"]:
290 self.__exclude.append(value) 301 self.__exclude.append(value)
291 302
292 elif key == "@require": 303 elif key == "@require":
293 requireList.append(value) 304 self.__require.append(value)
294 305
295 elif key == "@run-at": 306 elif key == "@run-at":
296 if value == "document-end": 307 if value == "document-end":
297 self.__startAt = GreaseMonkeyScript.DocumentEnd 308 self.__startAt = GreaseMonkeyScript.DocumentEnd
298 elif value == "document-start": 309 elif value == "document-start":
311 322
312 nspace = bytes(QCryptographicHash.hash( 323 nspace = bytes(QCryptographicHash.hash(
313 QByteArray(self.fullName().encode("utf-8")), 324 QByteArray(self.fullName().encode("utf-8")),
314 QCryptographicHash.Md4).toHex()).decode("ascii") 325 QCryptographicHash.Md4).toHex()).decode("ascii")
315 valuesScript = values_js.format(nspace) 326 valuesScript = values_js.format(nspace)
316 runCheck = """ 327 if qVersion() < "5.8.0":
317 for (var value of {0}) {{ 328 runCheck = """
318 var re = new RegExp(value); 329 for (var value of {0}) {{
319 if (re.test(window.location.href)) {{ 330 var re = new RegExp(value);
331 if (re.test(window.location.href)) {{
332 return;
333 }}
334 }}
335 __eric_includes = false;
336 for (var value of {1}) {{
337 var re = new RegExp(value);
338 if (re.test(window.location.href)) {{
339 __eric_includes = true;
340 break;
341 }}
342 }}
343 if (!__eric_includes) {{
320 return; 344 return;
321 }} 345 }}
322 }} 346 delete __eric_includes;""".format(
323 __eric_includes = false; 347 self.__toJavaScriptList(self.__exclude[:]),
324 for (var value of {1}) {{ 348 self.__toJavaScriptList(self.__include[:])
325 var re = new RegExp(value); 349 )
326 if (re.test(window.location.href)) {{ 350 self.__script = "(function(){{{0}\n{1}\n{2}\n{3}\n}})();".format(
327 __eric_includes = true; 351 runCheck, valuesScript,
328 break; 352 self.__manager.requireScripts(self.__require), fileData
329 }} 353 )
330 }} 354 else:
331 if (!__eric_includes) {{ 355 self.__script = "(function(){{{0}\n{1}\n{2}\n}})();".format(
332 return; 356 valuesScript, self.__manager.requireScripts(self.__require),
333 }} 357 fileData
334 delete __eric_includes;""".format( 358 )
335 self.__toJavaScriptList(self.__exclude[:]),
336 self.__toJavaScriptList(self.__include[:])
337 )
338 runCheck = ""
339 self.__script = "(function(){{{0}\n{1}\n{2}\n{3}\n}})();".format(
340 runCheck, valuesScript,
341 self.__manager.requireScripts(requireList), fileData
342 )
343 self.__valid = True 359 self.__valid = True
344 360
345 def webScript(self): 361 def webScript(self):
346 """ 362 """
347 Public method to create a script object. 363 Public method to create a script object.
348 364
349 @return prepared script object 365 @return prepared script object
350 @rtype QWebEngineScript 366 @rtype QWebEngineScript
351 @exception ValueError raised to indicate an unsupported start point 367 @exception ValueError raised to indicate an unsupported start point
352 """ 368 """
353 if self.startAt() == GreaseMonkeyScript.DocumentStart: 369 if qVersion() < "5.8.0":
354 injectionPoint = QWebEngineScript.DocumentCreation 370 if self.startAt() == GreaseMonkeyScript.DocumentStart:
355 elif self.startAt() == GreaseMonkeyScript.DocumentEnd: 371 injectionPoint = QWebEngineScript.DocumentCreation
356 injectionPoint = QWebEngineScript.DocumentReady 372 elif self.startAt() == GreaseMonkeyScript.DocumentEnd:
357 elif self.startAt() == GreaseMonkeyScript.DocumentIdle: 373 injectionPoint = QWebEngineScript.DocumentReady
358 injectionPoint = QWebEngineScript.Deferred 374 elif self.startAt() == GreaseMonkeyScript.DocumentIdle:
359 else: 375 injectionPoint = QWebEngineScript.Deferred
360 raise ValueError("Wrong script start point.") 376 else:
377 raise ValueError("Wrong script start point.")
361 378
362 script = QWebEngineScript() 379 script = QWebEngineScript()
363 script.setSourceCode("{0}\n{1}".format( 380 script.setSourceCode("{0}\n{1}".format(
364 bootstrap_js, self.__script 381 bootstrap_js, self.__script
365 )) 382 ))
366 script.setName(self.fullName()) 383 script.setName(self.fullName())
367 script.setInjectionPoint(injectionPoint) 384 if qVersion() < "5.8.0":
385 script.setInjectionPoint(injectionPoint)
368 script.setWorldId(WebBrowserPage.SafeJsWorld) 386 script.setWorldId(WebBrowserPage.SafeJsWorld)
369 script.setRunsOnSubFrames(not self.__noFrames) 387 script.setRunsOnSubFrames(not self.__noFrames)
370 return script 388 return script
371 389
372 def __toJavaScriptList(self, patterns): 390 def __toJavaScriptList(self, patterns):
377 @param patterns list of match patterns 395 @param patterns list of match patterns
378 @type list of str 396 @type list of str
379 @return JavaScript script containing the list 397 @return JavaScript script containing the list
380 @rtype str 398 @rtype str
381 """ 399 """
382 patternList = [] 400 if qVersion() >= "5.8.0":
383 for pattern in patterns: 401 script = ""
384 if pattern.startswith("/") and pattern.endswith("/") and \ 402 else:
385 len(pattern) > 1: 403 patternList = []
386 pattern = pattern[1:-1] 404 for pattern in patterns:
387 else: 405 if pattern.startswith("/") and pattern.endswith("/") and \
388 pattern = pattern.replace(".", "\\.").replace("*", ".*") 406 len(pattern) > 1:
389 pattern = "'{0}'".format(pattern) 407 pattern = pattern[1:-1]
390 patternList.append(pattern) 408 else:
391 409 pattern = pattern.replace(".", "\\.").replace("*", ".*")
392 script = "[{0}]".format(",".join(patternList)) 410 pattern = "'{0}'".format(pattern)
411 patternList.append(pattern)
412
413 script = "[{0}]".format(",".join(patternList))
393 return script 414 return script
415
416 def updateScript(self):
417 """
418 Public method to updated the script.
419 """
420 if not self.__downloadUrl.isValid() or self.__updating:
421 return
422
423 self.__updating = True
424 self.updatingChanged.emit(self.__updating)
425
426 downloader = GreaseMonkeyDownloader(
427 self.__downloadUrl,
428 self.__manager,
429 GreaseMonkeyDownloader.DownloadMainScript)
430 downloader.updateScript(self.__fileName)
431 downloader.finished.connect(self.__downloaderFinished)
432 downloader.error.connect(self.__downloaderError)
433 self.__downloaders.append(downloader)
434
435 self.__downloadRequires()
436
437 def __downloaderFinished(self):
438 """
439 Private slot to handle a finished download.
440 """
441 downloader = self.sender()
442 if downloader in self.__downloaders:
443 self.__downloaders.remove(downloader)
444 self.__updating = False
445 self.updatingChanged.emit(self.__updating)
446
447 def __downloaderError(self):
448 """
449 Private slot to handle a downloader error.
450 """
451 downloader = self.sender()
452 if downloader in self.__downloaders:
453 self.__downloaders.remove(downloader)
454 self.__updating = False
455 self.updatingChanged.emit(self.__updating)
456
457 def __reloadScript(self):
458 """
459 Private method to reload the script.
460 """
461 self.__parseScript()
462
463 self.__manager.removeScript(self, False)
464 self.__manager.addScript(self)
465
466 self.scriptChanged.emit()
467
468 def __downloadRequires(self):
469 """
470 Private method to download the required scripts.
471 """
472 for urlStr in self.__require:
473 if not self.__manager.requireScripts([urlStr]):
474 downloader = GreaseMonkeyDownloader(
475 QUrl(urlStr),
476 self.__manager,
477 GreaseMonkeyDownloader.DownloadRequireScript)
478 downloader.finished.connect(self.__requireDownloaded)
479 downloader.error.connect(self.__requireDownloadError)
480 self.__downloaders.append(downloader)
481
482 def __requireDownloaded(self):
483 """
484 Private slot to handle a finished download of a required script.
485 """
486 downloader = self.sender()
487 if downloader in self.__downloaders:
488 self.__downloaders.remove(downloader)
489
490 self.__reloadScript()
491
492 def __requireDownloadError(self):
493 """
494 Private slot to handle a downloader error.
495 """
496 downloader = self.sender()
497 if downloader in self.__downloaders:
498 self.__downloaders.remove(downloader)

eric ide

mercurial