src/eric7/WebBrowser/WebBrowserView.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9153
506e35e424d5
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2008 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6
7 """
8 Module implementing the web browser using QWebEngineView.
9 """
10
11 import os
12 import functools
13 import contextlib
14 import pathlib
15
16 from PyQt6.QtCore import (
17 pyqtSignal, pyqtSlot, Qt, QUrl, QTimer, QEvent, QPoint, QPointF, QDateTime,
18 QStandardPaths, QByteArray, QIODevice, QDataStream
19 )
20 from PyQt6.QtGui import (
21 QDesktopServices, QClipboard, QIcon, QContextMenuEvent, QPixmap, QCursor
22 )
23 from PyQt6.QtWidgets import QStyle, QMenu, QApplication, QDialog
24 from PyQt6.QtWebEngineCore import QWebEnginePage, QWebEngineDownloadRequest
25 from PyQt6.QtWebEngineWidgets import QWebEngineView
26
27 from EricWidgets import EricMessageBox, EricFileDialog
28 from EricWidgets.EricApplication import ericApp
29
30 from WebBrowser.WebBrowserWindow import WebBrowserWindow
31 from .WebBrowserPage import WebBrowserPage
32
33 from .Tools.WebIconLoader import WebIconLoader
34 from .Tools import Scripts
35
36 from . import WebInspector
37 from .Tools.WebBrowserTools import getHtmlPage, pixmapToDataUrl
38
39 import Preferences
40 import UI.PixmapCache
41 import Utilities
42
43
44 class WebBrowserView(QWebEngineView):
45 """
46 Class implementing the web browser view widget.
47
48 @signal sourceChanged(QUrl) emitted after the current URL has changed
49 @signal forwardAvailable(bool) emitted after the current URL has changed
50 @signal backwardAvailable(bool) emitted after the current URL has changed
51 @signal highlighted(str) emitted, when the mouse hovers over a link
52 @signal search(QUrl) emitted, when a search is requested
53 @signal zoomValueChanged(int) emitted to signal a change of the zoom value
54 @signal faviconChanged() emitted to signal a changed web site icon
55 @signal safeBrowsingAbort() emitted to indicate an abort due to a safe
56 browsing event
57 @signal safeBrowsingBad(threatType, threatMessages) emitted to indicate a
58 malicious web site as determined by safe browsing
59 @signal showMessage(str) emitted to show a message in the main window
60 status bar
61 """
62 sourceChanged = pyqtSignal(QUrl)
63 forwardAvailable = pyqtSignal(bool)
64 backwardAvailable = pyqtSignal(bool)
65 highlighted = pyqtSignal(str)
66 search = pyqtSignal(QUrl)
67 zoomValueChanged = pyqtSignal(int)
68 faviconChanged = pyqtSignal()
69 safeBrowsingAbort = pyqtSignal()
70 safeBrowsingBad = pyqtSignal(str, str)
71 showMessage = pyqtSignal(str)
72
73 ZoomLevels = [
74 30, 40, 50, 67, 80, 90,
75 100,
76 110, 120, 133, 150, 170, 200, 220, 233, 250, 270, 285, 300,
77 ]
78 ZoomLevelDefault = 100
79
80 def __init__(self, mainWindow, parent=None, name=""):
81 """
82 Constructor
83
84 @param mainWindow reference to the main window (WebBrowserWindow)
85 @param parent parent widget of this window (QWidget)
86 @param name name of this window (string)
87 """
88 super().__init__(parent)
89 self.setObjectName(name)
90
91 self.__rwhvqt = None
92 self.installEventFilter(self)
93
94 self.__speedDial = WebBrowserWindow.speedDial()
95
96 self.__page = None
97 self.__createNewPage()
98
99 self.__mw = mainWindow
100 self.__tabWidget = parent
101 self.__isLoading = False
102 self.__progress = 0
103 self.__siteIconLoader = None
104 self.__siteIcon = QIcon()
105 self.__menu = QMenu(self)
106 self.__clickedPos = QPoint()
107 self.__firstLoad = False
108 self.__preview = QPixmap()
109
110 self.__currentZoom = 100
111 self.__zoomLevels = WebBrowserView.ZoomLevels[:]
112
113 self.iconUrlChanged.connect(self.__iconUrlChanged)
114 self.urlChanged.connect(self.__urlChanged)
115 self.page().linkHovered.connect(self.__linkHovered)
116
117 self.loadStarted.connect(self.__loadStarted)
118 self.loadProgress.connect(self.__loadProgress)
119 self.loadFinished.connect(self.__loadFinished)
120 self.renderProcessTerminated.connect(self.__renderProcessTerminated)
121
122 self.__mw.openSearchManager().currentEngineChanged.connect(
123 self.__currentEngineChanged)
124
125 self.setAcceptDrops(True)
126
127 self.__rss = []
128
129 self.__clickedFrame = None
130
131 self.__mw.personalInformationManager().connectPage(self.page())
132
133 self.__inspector = None
134 WebInspector.registerView(self)
135
136 self.__restoreData = None
137
138 if self.parentWidget() is not None:
139 self.parentWidget().installEventFilter(self)
140
141 self.grabGesture(Qt.GestureType.PinchGesture)
142
143 def __createNewPage(self):
144 """
145 Private method to create a new page object.
146 """
147 self.__page = WebBrowserPage(self, self)
148 self.setPage(self.__page)
149
150 self.__page.safeBrowsingAbort.connect(self.safeBrowsingAbort)
151 self.__page.safeBrowsingBad.connect(self.safeBrowsingBad)
152 self.__page.printPageRequested.connect(self.__printPage)
153 self.__page.quotaRequested.connect(self.__quotaRequested)
154 # The registerProtocolHandlerRequested signal is handled in
155 # WebBrowserPage.
156 self.__page.selectClientCertificate.connect(
157 self.__selectClientCertificate)
158 self.__page.findTextFinished.connect(self.__findTextFinished)
159
160 def __setRwhvqt(self):
161 """
162 Private slot to set widget that receives input events.
163 """
164 self.grabGesture(Qt.GestureType.PinchGesture)
165 self.__rwhvqt = self.focusProxy()
166 if self.__rwhvqt:
167 self.__rwhvqt.grabGesture(Qt.GestureType.PinchGesture)
168 self.__rwhvqt.installEventFilter(self)
169 else:
170 print("Focus proxy is null!") # __IGNORE_WARNING_M801__
171
172 def __currentEngineChanged(self):
173 """
174 Private slot to track a change of the current search engine.
175 """
176 if self.url().toString() == "eric:home":
177 self.reload()
178
179 def mainWindow(self):
180 """
181 Public method to get a reference to the main window.
182
183 @return reference to the main window
184 @rtype WebBrowserWindow
185 """
186 return self.__mw
187
188 def tabWidget(self):
189 """
190 Public method to get a reference to the tab widget containing this
191 view.
192
193 @return reference to the tab widget
194 @rtype WebBrowserTabWidget
195 """
196 return self.__tabWidget
197
198 def load(self, url):
199 """
200 Public method to load a web site.
201
202 @param url URL to be loaded
203 @type QUrl
204 """
205 if (
206 self.__page is not None and
207 not self.__page.acceptNavigationRequest(
208 url, QWebEnginePage.NavigationType.NavigationTypeTyped, True)
209 ):
210 return
211
212 super().load(url)
213
214 if not self.__firstLoad:
215 self.__firstLoad = True
216 WebInspector.pushView(self)
217
218 def setSource(self, name, newTab=False):
219 """
220 Public method used to set the source to be displayed.
221
222 @param name filename to be shown (QUrl)
223 @param newTab flag indicating to open the URL in a new tab (bool)
224 """
225 if name is None or not name.isValid():
226 return
227
228 if newTab:
229 # open in a new tab
230 self.__mw.newTab(name)
231 return
232
233 if not name.scheme():
234 if not os.path.exists(name.toString()):
235 name.setScheme(Preferences.getWebBrowser("DefaultScheme"))
236 else:
237 if Utilities.isWindowsPlatform():
238 name.setUrl("file:///" + Utilities.fromNativeSeparators(
239 name.toString()))
240 else:
241 name.setUrl("file://" + name.toString())
242
243 if (
244 len(name.scheme()) == 1 or
245 name.scheme() == "file"
246 ):
247 # name is a local file
248 if name.scheme() and len(name.scheme()) == 1:
249 # it is a local path on win os
250 name = QUrl.fromLocalFile(name.toString())
251
252 if not pathlib.Path(name.toLocalFile()).exists():
253 EricMessageBox.critical(
254 self,
255 self.tr("eric Web Browser"),
256 self.tr(
257 """<p>The file <b>{0}</b> does not exist.</p>""")
258 .format(name.toLocalFile()))
259 return
260
261 if name.toLocalFile().lower().endswith((".pdf", ".chm")):
262 started = QDesktopServices.openUrl(name)
263 if not started:
264 EricMessageBox.critical(
265 self,
266 self.tr("eric Web Browser"),
267 self.tr(
268 """<p>Could not start a viewer"""
269 """ for file <b>{0}</b>.</p>""")
270 .format(name.path()))
271 return
272 elif name.scheme() in ["mailto"]:
273 started = QDesktopServices.openUrl(name)
274 if not started:
275 EricMessageBox.critical(
276 self,
277 self.tr("eric Web Browser"),
278 self.tr(
279 """<p>Could not start an application"""
280 """ for URL <b>{0}</b>.</p>""")
281 .format(name.toString()))
282 return
283 else:
284 if name.toString().lower().endswith((".pdf", ".chm")):
285 started = QDesktopServices.openUrl(name)
286 if not started:
287 EricMessageBox.critical(
288 self,
289 self.tr("eric Web Browser"),
290 self.tr(
291 """<p>Could not start a viewer"""
292 """ for file <b>{0}</b>.</p>""")
293 .format(name.path()))
294 return
295
296 self.load(name)
297
298 def source(self):
299 """
300 Public method to return the URL of the loaded page.
301
302 @return URL loaded in the help browser (QUrl)
303 """
304 return self.url()
305
306 def documentTitle(self):
307 """
308 Public method to return the title of the loaded page.
309
310 @return title (string)
311 """
312 return self.title()
313
314 def backward(self):
315 """
316 Public slot to move backwards in history.
317 """
318 self.triggerPageAction(QWebEnginePage.WebAction.Back)
319 self.__urlChanged(self.history().currentItem().url())
320
321 def forward(self):
322 """
323 Public slot to move forward in history.
324 """
325 self.triggerPageAction(QWebEnginePage.WebAction.Forward)
326 self.__urlChanged(self.history().currentItem().url())
327
328 def home(self):
329 """
330 Public slot to move to the first page loaded.
331 """
332 homeUrl = QUrl(Preferences.getWebBrowser("HomePage"))
333 self.setSource(homeUrl)
334 self.__urlChanged(self.history().currentItem().url())
335
336 def reload(self):
337 """
338 Public slot to reload the current page.
339 """
340 self.triggerPageAction(QWebEnginePage.WebAction.Reload)
341
342 def reloadBypassingCache(self):
343 """
344 Public slot to reload the current page bypassing the cache.
345 """
346 self.triggerPageAction(QWebEnginePage.WebAction.ReloadAndBypassCache)
347
348 def copy(self):
349 """
350 Public slot to copy the selected text.
351 """
352 self.triggerPageAction(QWebEnginePage.WebAction.Copy)
353
354 def cut(self):
355 """
356 Public slot to cut the selected text.
357 """
358 self.triggerPageAction(QWebEnginePage.WebAction.Cut)
359
360 def paste(self):
361 """
362 Public slot to paste text from the clipboard.
363 """
364 self.triggerPageAction(QWebEnginePage.WebAction.Paste)
365
366 def undo(self):
367 """
368 Public slot to undo the last edit action.
369 """
370 self.triggerPageAction(QWebEnginePage.WebAction.Undo)
371
372 def redo(self):
373 """
374 Public slot to redo the last edit action.
375 """
376 self.triggerPageAction(QWebEnginePage.WebAction.Redo)
377
378 def selectAll(self):
379 """
380 Public slot to select all text.
381 """
382 self.triggerPageAction(QWebEnginePage.WebAction.SelectAll)
383
384 def unselect(self):
385 """
386 Public slot to clear the current selection.
387 """
388 self.triggerPageAction(QWebEnginePage.WebAction.Unselect)
389
390 def isForwardAvailable(self):
391 """
392 Public method to determine, if a forward move in history is possible.
393
394 @return flag indicating move forward is possible (boolean)
395 """
396 return self.history().canGoForward()
397
398 def isBackwardAvailable(self):
399 """
400 Public method to determine, if a backwards move in history is possible.
401
402 @return flag indicating move backwards is possible (boolean)
403 """
404 return self.history().canGoBack()
405
406 def __levelForZoom(self, zoom):
407 """
408 Private method determining the zoom level index given a zoom factor.
409
410 @param zoom zoom factor (integer)
411 @return index of zoom factor (integer)
412 """
413 try:
414 index = self.__zoomLevels.index(zoom)
415 except ValueError:
416 for index in range(len(self.__zoomLevels)):
417 if zoom <= self.__zoomLevels[index]:
418 break
419 return index
420
421 def setZoomValue(self, value, saveValue=True):
422 """
423 Public method to set the zoom value.
424
425 @param value zoom value (integer)
426 @param saveValue flag indicating to save the zoom value with the
427 zoom manager
428 @type bool
429 """
430 if value != self.__currentZoom:
431 self.setZoomFactor(value / 100.0)
432 self.__currentZoom = value
433 if saveValue and not self.__mw.isPrivate():
434 from .ZoomManager import ZoomManager
435 ZoomManager.instance().setZoomValue(self.url(), value)
436 self.zoomValueChanged.emit(value)
437
438 def zoomValue(self):
439 """
440 Public method to get the current zoom value.
441
442 @return zoom value (integer)
443 """
444 val = self.zoomFactor() * 100
445 return int(val)
446
447 def zoomIn(self):
448 """
449 Public slot to zoom into the page.
450 """
451 index = self.__levelForZoom(self.__currentZoom)
452 if index < len(self.__zoomLevels) - 1:
453 self.setZoomValue(self.__zoomLevels[index + 1])
454
455 def zoomOut(self):
456 """
457 Public slot to zoom out of the page.
458 """
459 index = self.__levelForZoom(self.__currentZoom)
460 if index > 0:
461 self.setZoomValue(self.__zoomLevels[index - 1])
462
463 def zoomReset(self):
464 """
465 Public method to reset the zoom factor.
466 """
467 index = self.__levelForZoom(WebBrowserView.ZoomLevelDefault)
468 self.setZoomValue(self.__zoomLevels[index])
469
470 def mapToViewport(self, pos):
471 """
472 Public method to map a position to the viewport.
473
474 @param pos position to be mapped
475 @type QPoint
476 @return viewport position
477 @rtype QPoint
478 """
479 return self.page().mapToViewport(pos)
480
481 def hasSelection(self):
482 """
483 Public method to determine, if there is some text selected.
484
485 @return flag indicating text has been selected (boolean)
486 """
487 return self.selectedText() != ""
488
489 def findNextPrev(self, txt, case, backwards, callback):
490 """
491 Public slot to find the next occurrence of a text.
492
493 @param txt text to search for (string)
494 @param case flag indicating a case sensitive search (boolean)
495 @param backwards flag indicating a backwards search (boolean)
496 @param callback reference to a function with a bool parameter
497 @type function(bool) or None
498 """
499 findFlags = QWebEnginePage.FindFlag(0)
500 if case:
501 findFlags |= QWebEnginePage.FindFlag.FindCaseSensitively
502 if backwards:
503 findFlags |= QWebEnginePage.FindFlag.FindBackward
504
505 if callback is None:
506 self.findText(txt, findFlags)
507 else:
508 self.findText(txt, findFlags, callback)
509
510 def __findTextFinished(self, result):
511 """
512 Private slot handling the findTextFinished signal of the web page.
513
514 @param result reference to the QWebEngineFindTextResult object of the
515 last search
516 @type QWebEngineFindTextResult
517 """
518 self.showMessage.emit(self.tr("Match {0} of {1}").format(
519 result.activeMatch(), result.numberOfMatches())
520 )
521
522 def contextMenuEvent(self, evt):
523 """
524 Protected method called to create a context menu.
525
526 This method is overridden from QWebEngineView.
527
528 @param evt reference to the context menu event object
529 (QContextMenuEvent)
530 """
531 pos = evt.pos()
532 reason = evt.reason()
533 QTimer.singleShot(
534 0, functools.partial(
535 self._contextMenuEvent, QContextMenuEvent(reason, pos)))
536 # needs to be done this way because contextMenuEvent is blocking
537 # the main loop
538
539 def _contextMenuEvent(self, evt):
540 """
541 Protected method called to create a context menu.
542
543 @param evt reference to the context menu event object
544 (QContextMenuEvent)
545 """
546 self.__menu.clear()
547
548 hitTest = self.page().hitTestContent(evt.pos())
549
550 self.__createContextMenu(self.__menu, hitTest)
551
552 if not hitTest.isContentEditable() and not hitTest.isContentSelected():
553 self.__menu.addSeparator()
554 self.__menu.addMenu(self.__mw.adBlockIcon().menu())
555
556 self.__menu.addSeparator()
557 self.__menu.addAction(
558 UI.PixmapCache.getIcon("webInspector"),
559 self.tr("Inspect Element..."), self.__webInspector)
560
561 if not self.__menu.isEmpty():
562 pos = evt.globalPos()
563 self.__menu.popup(QPoint(pos.x(), pos.y() + 1))
564
565 def __createContextMenu(self, menu, hitTest):
566 """
567 Private method to populate the context menu.
568
569 @param menu reference to the menu to be populated
570 @type QMenu
571 @param hitTest reference to the hit test object
572 @type WebHitTestResult
573 """
574 spellCheckActionCount = 0
575 contextMenuData = self.lastContextMenuRequest()
576 hitTest.updateWithContextMenuData(contextMenuData)
577
578 if bool(contextMenuData.misspelledWord()):
579 boldFont = menu.font()
580 boldFont.setBold(True)
581
582 for suggestion in contextMenuData.spellCheckerSuggestions():
583 act = menu.addAction(suggestion)
584 act.setFont(boldFont)
585 act.triggered.connect(
586 functools.partial(self.__replaceMisspelledWord, act))
587
588 if not bool(menu.actions()):
589 menu.addAction(self.tr("No suggestions")).setEnabled(False)
590
591 menu.addSeparator()
592 spellCheckActionCount = len(menu.actions())
593
594 if (
595 not hitTest.linkUrl().isEmpty() and
596 hitTest.linkUrl().scheme() != "javascript"
597 ):
598 self.__createLinkContextMenu(menu, hitTest)
599
600 if not hitTest.imageUrl().isEmpty():
601 self.__createImageContextMenu(menu, hitTest)
602
603 if not hitTest.mediaUrl().isEmpty():
604 self.__createMediaContextMenu(menu, hitTest)
605
606 if hitTest.isContentEditable():
607 # check, if only spell checker actions were added
608 if len(menu.actions()) == spellCheckActionCount:
609 menu.addAction(self.__mw.undoAct)
610 menu.addAction(self.__mw.redoAct)
611 menu.addSeparator()
612 menu.addAction(self.__mw.cutAct)
613 menu.addAction(self.__mw.copyAct)
614 menu.addAction(self.__mw.pasteAct)
615 menu.addSeparator()
616 self.__mw.personalInformationManager().createSubMenu(
617 menu, self, hitTest)
618
619 if hitTest.tagName() == "input":
620 menu.addSeparator()
621 act = menu.addAction("")
622 act.setVisible(False)
623 self.__checkForForm(act, hitTest.pos())
624
625 if self.selectedText():
626 self.__createSelectedTextContextMenu(menu, hitTest)
627
628 if self.__menu.isEmpty():
629 self.__createPageContextMenu(menu)
630
631 def __createLinkContextMenu(self, menu, hitTest):
632 """
633 Private method to populate the context menu for URLs.
634
635 @param menu reference to the menu to be populated
636 @type QMenu
637 @param hitTest reference to the hit test object
638 @type WebHitTestResult
639 """
640 if not menu.isEmpty():
641 menu.addSeparator()
642
643 act = menu.addAction(
644 UI.PixmapCache.getIcon("openNewTab"),
645 self.tr("Open Link in New Tab\tCtrl+LMB"))
646 act.setData(hitTest.linkUrl())
647 act.triggered.connect(
648 functools.partial(self.__openLinkInNewTab, act))
649 act = menu.addAction(
650 UI.PixmapCache.getIcon("newWindow"),
651 self.tr("Open Link in New Window"))
652 act.setData(hitTest.linkUrl())
653 act.triggered.connect(
654 functools.partial(self.__openLinkInNewWindow, act))
655 act = menu.addAction(
656 UI.PixmapCache.getIcon("privateMode"),
657 self.tr("Open Link in New Private Window"))
658 act.setData(hitTest.linkUrl())
659 act.triggered.connect(
660 functools.partial(self.__openLinkInNewPrivateWindow, act))
661 menu.addSeparator()
662 menu.addAction(
663 UI.PixmapCache.getIcon("download"),
664 self.tr("Save Lin&k"), self.__downloadLink)
665 act = menu.addAction(
666 UI.PixmapCache.getIcon("bookmark22"),
667 self.tr("Bookmark this Link"))
668 act.setData(hitTest.linkUrl())
669 act.triggered.connect(
670 functools.partial(self.__bookmarkLink, act))
671 menu.addSeparator()
672 act = menu.addAction(
673 UI.PixmapCache.getIcon("editCopy"),
674 self.tr("Copy URL to Clipboard"))
675 act.setData(hitTest.linkUrl())
676 act.triggered.connect(
677 functools.partial(self.__copyLink, act))
678 act = menu.addAction(
679 UI.PixmapCache.getIcon("mailSend"),
680 self.tr("Send URL"))
681 act.setData(hitTest.linkUrl())
682 act.triggered.connect(
683 functools.partial(self.__sendLink, act))
684 if (
685 Preferences.getWebBrowser("VirusTotalEnabled") and
686 Preferences.getWebBrowser("VirusTotalServiceKey") != ""
687 ):
688 act = menu.addAction(
689 UI.PixmapCache.getIcon("virustotal"),
690 self.tr("Scan Link with VirusTotal"))
691 act.setData(hitTest.linkUrl())
692 act.triggered.connect(
693 functools.partial(self.__virusTotal, act))
694
695 def __createImageContextMenu(self, menu, hitTest):
696 """
697 Private method to populate the context menu for images.
698
699 @param menu reference to the menu to be populated
700 @type QMenu
701 @param hitTest reference to the hit test object
702 @type WebHitTestResult
703 """
704 if not menu.isEmpty():
705 menu.addSeparator()
706
707 act = menu.addAction(
708 UI.PixmapCache.getIcon("openNewTab"),
709 self.tr("Open Image in New Tab"))
710 act.setData(hitTest.imageUrl())
711 act.triggered.connect(
712 functools.partial(self.__openLinkInNewTab, act))
713 menu.addSeparator()
714 menu.addAction(
715 UI.PixmapCache.getIcon("download"),
716 self.tr("Save Image"), self.__downloadImage)
717 menu.addAction(
718 self.tr("Copy Image to Clipboard"), self.__copyImage)
719 act = menu.addAction(
720 UI.PixmapCache.getIcon("editCopy"),
721 self.tr("Copy Image URL to Clipboard"))
722 act.setData(hitTest.imageUrl())
723 act.triggered.connect(
724 functools.partial(self.__copyLink, act))
725 act = menu.addAction(
726 UI.PixmapCache.getIcon("mailSend"),
727 self.tr("Send Image URL"))
728 act.setData(hitTest.imageUrl())
729 act.triggered.connect(
730 functools.partial(self.__sendLink, act))
731
732 if hitTest.imageUrl().scheme() in ["http", "https"]:
733 menu.addSeparator()
734 engine = WebBrowserWindow.imageSearchEngine()
735 searchEngineName = engine.searchEngine()
736 act = menu.addAction(
737 UI.PixmapCache.getIcon("{0}".format(
738 searchEngineName.lower())),
739 self.tr("Search image in {0}").format(searchEngineName))
740 act.setData(engine.getSearchQuery(hitTest.imageUrl()))
741 act.triggered.connect(
742 functools.partial(self.__searchImage, act))
743 self.__imageSearchMenu = menu.addMenu(
744 self.tr("Search image with..."))
745 for searchEngineName in engine.searchEngineNames():
746 act = self.__imageSearchMenu.addAction(
747 UI.PixmapCache.getIcon("{0}".format(
748 searchEngineName.lower())),
749 self.tr("Search image in {0}").format(searchEngineName))
750 act.setData(engine.getSearchQuery(
751 hitTest.imageUrl(), searchEngineName))
752 act.triggered.connect(
753 functools.partial(self.__searchImage, act))
754
755 menu.addSeparator()
756 act = menu.addAction(
757 UI.PixmapCache.getIcon("adBlockPlus"),
758 self.tr("Block Image"))
759 act.setData(hitTest.imageUrl().toString())
760 act.triggered.connect(
761 functools.partial(self.__blockImage, act))
762 if (
763 Preferences.getWebBrowser("VirusTotalEnabled") and
764 Preferences.getWebBrowser("VirusTotalServiceKey") != ""
765 ):
766 act = menu.addAction(
767 UI.PixmapCache.getIcon("virustotal"),
768 self.tr("Scan Image with VirusTotal"))
769 act.setData(hitTest.imageUrl())
770 act.triggered.connect(
771 functools.partial(self.__virusTotal, act))
772
773 def __createMediaContextMenu(self, menu, hitTest):
774 """
775 Private method to populate the context menu for media elements.
776
777 @param menu reference to the menu to be populated
778 @type QMenu
779 @param hitTest reference to the hit test object
780 @type WebHitTestResult
781 """
782 if not menu.isEmpty():
783 menu.addSeparator()
784
785 if hitTest.mediaPaused():
786 menu.addAction(
787 UI.PixmapCache.getIcon("mediaPlaybackStart"),
788 self.tr("Play"), self.__pauseMedia)
789 else:
790 menu.addAction(
791 UI.PixmapCache.getIcon("mediaPlaybackPause"),
792 self.tr("Pause"), self.__pauseMedia)
793 if hitTest.mediaMuted():
794 menu.addAction(
795 UI.PixmapCache.getIcon("audioVolumeHigh"),
796 self.tr("Unmute"), self.__muteMedia)
797 else:
798 menu.addAction(
799 UI.PixmapCache.getIcon("audioVolumeMuted"),
800 self.tr("Mute"), self.__muteMedia)
801 menu.addSeparator()
802 act = menu.addAction(
803 UI.PixmapCache.getIcon("editCopy"),
804 self.tr("Copy Media URL to Clipboard"))
805 act.setData(hitTest.mediaUrl())
806 act.triggered.connect(
807 functools.partial(self.__copyLink, act))
808 act = menu.addAction(
809 UI.PixmapCache.getIcon("mailSend"),
810 self.tr("Send Media URL"))
811 act.setData(hitTest.mediaUrl())
812 act.triggered.connect(
813 functools.partial(self.__sendLink, act))
814 menu.addAction(
815 UI.PixmapCache.getIcon("download"),
816 self.tr("Save Media"), self.__downloadMedia)
817
818 def __createSelectedTextContextMenu(self, menu, hitTest):
819 """
820 Private method to populate the context menu for selected text.
821
822 @param menu reference to the menu to be populated
823 @type QMenu
824 @param hitTest reference to the hit test object
825 @type WebHitTestResult
826 """
827 if not menu.isEmpty():
828 menu.addSeparator()
829
830 menu.addAction(self.__mw.copyAct)
831 menu.addSeparator()
832 act = menu.addAction(
833 UI.PixmapCache.getIcon("mailSend"),
834 self.tr("Send Text"))
835 act.setData(self.selectedText())
836 act.triggered.connect(
837 functools.partial(self.__sendLink, act))
838
839 engineName = self.__mw.openSearchManager().currentEngineName()
840 if engineName:
841 menu.addAction(self.tr("Search with '{0}'").format(engineName),
842 self.__searchDefaultRequested)
843
844 from .OpenSearch.OpenSearchEngineAction import (
845 OpenSearchEngineAction
846 )
847
848 self.__searchMenu = menu.addMenu(self.tr("Search with..."))
849 engineNames = self.__mw.openSearchManager().allEnginesNames()
850 for engineName in engineNames:
851 engine = self.__mw.openSearchManager().engine(engineName)
852 act = OpenSearchEngineAction(engine, self.__searchMenu)
853 act.setData(engineName)
854 self.__searchMenu.addAction(act)
855 self.__searchMenu.triggered.connect(self.__searchRequested)
856
857 menu.addSeparator()
858
859 from .WebBrowserLanguagesDialog import WebBrowserLanguagesDialog
860 languages = Preferences.toList(
861 Preferences.getSettings().value(
862 "WebBrowser/AcceptLanguages",
863 WebBrowserLanguagesDialog.defaultAcceptLanguages()))
864 if languages:
865 language = languages[0]
866 langCode = language.split("[")[1][:2]
867 googleTranslatorUrl = QUrl(
868 "http://translate.google.com/#auto/{0}/{1}".format(
869 langCode, self.selectedText()))
870 act = menu.addAction(
871 UI.PixmapCache.getIcon("translate"),
872 self.tr("Google Translate"))
873 act.setData(googleTranslatorUrl)
874 act.triggered.connect(
875 functools.partial(self.__openLinkInNewTab, act))
876 wiktionaryUrl = QUrl(
877 "http://{0}.wiktionary.org/wiki/Special:Search?search={1}"
878 .format(langCode, self.selectedText()))
879 act = menu.addAction(
880 UI.PixmapCache.getIcon("wikipedia"),
881 self.tr("Dictionary"))
882 act.setData(wiktionaryUrl)
883 act.triggered.connect(
884 functools.partial(self.__openLinkInNewTab, act))
885 menu.addSeparator()
886
887 guessedUrl = QUrl.fromUserInput(self.selectedText().strip())
888 if self.__isUrlValid(guessedUrl):
889 act = menu.addAction(self.tr("Go to web address"))
890 act.setData(guessedUrl)
891 act.triggered.connect(
892 functools.partial(self.__openLinkInNewTab, act))
893
894 def __createPageContextMenu(self, menu):
895 """
896 Private method to populate the basic context menu.
897
898 @param menu reference to the menu to be populated
899 @type QMenu
900 """
901 menu.addAction(self.__mw.newTabAct)
902 menu.addAction(self.__mw.newAct)
903 menu.addSeparator()
904 if self.__mw.saveAsAct is not None:
905 menu.addAction(self.__mw.saveAsAct)
906 menu.addAction(self.__mw.saveVisiblePageScreenAct)
907 menu.addSeparator()
908
909 if self.url().toString() == "eric:speeddial":
910 # special menu for the spedd dial page
911 menu.addAction(self.__mw.backAct)
912 menu.addAction(self.__mw.forwardAct)
913 menu.addSeparator()
914 menu.addAction(
915 UI.PixmapCache.getIcon("plus"),
916 self.tr("Add New Page"), self.__addSpeedDial)
917 menu.addAction(
918 UI.PixmapCache.getIcon("preferences-general"),
919 self.tr("Configure Speed Dial"), self.__configureSpeedDial)
920 menu.addSeparator()
921 menu.addAction(
922 UI.PixmapCache.getIcon("reload"),
923 self.tr("Reload All Dials"), self.__reloadAllSpeedDials)
924 menu.addSeparator()
925 menu.addAction(
926 self.tr("Reset to Default Dials"), self.__resetSpeedDials)
927 return
928
929 menu.addAction(
930 UI.PixmapCache.getIcon("bookmark22"),
931 self.tr("Bookmark this Page"), self.addBookmark)
932 act = menu.addAction(
933 UI.PixmapCache.getIcon("editCopy"),
934 self.tr("Copy Page URL to Clipboard"))
935 act.setData(self.url())
936 act.triggered.connect(
937 functools.partial(self.__copyLink, act))
938 act = menu.addAction(
939 UI.PixmapCache.getIcon("mailSend"),
940 self.tr("Send Page URL"))
941 act.setData(self.url())
942 act.triggered.connect(
943 functools.partial(self.__sendLink, act))
944 menu.addSeparator()
945
946 from .UserAgent.UserAgentMenu import UserAgentMenu
947 self.__userAgentMenu = UserAgentMenu(self.tr("User Agent"),
948 url=self.url())
949 menu.addMenu(self.__userAgentMenu)
950 menu.addSeparator()
951 menu.addAction(self.__mw.backAct)
952 menu.addAction(self.__mw.forwardAct)
953 menu.addAction(self.__mw.homeAct)
954 menu.addAction(self.__mw.reloadAct)
955 menu.addAction(self.__mw.stopAct)
956 menu.addSeparator()
957 menu.addAction(self.__mw.zoomInAct)
958 menu.addAction(self.__mw.zoomResetAct)
959 menu.addAction(self.__mw.zoomOutAct)
960 menu.addSeparator()
961 menu.addAction(self.__mw.selectAllAct)
962 menu.addSeparator()
963 menu.addAction(self.__mw.findAct)
964 menu.addSeparator()
965 menu.addAction(self.__mw.pageSourceAct)
966 menu.addSeparator()
967 menu.addAction(self.__mw.siteInfoAct)
968 if self.url().scheme() in ["http", "https"]:
969 menu.addSeparator()
970
971 w3url = QUrl.fromEncoded(
972 b"http://validator.w3.org/check?uri=" +
973 QUrl.toPercentEncoding(bytes(self.url().toEncoded()).decode()))
974 act = menu.addAction(
975 UI.PixmapCache.getIcon("w3"),
976 self.tr("Validate Page"))
977 act.setData(w3url)
978 act.triggered.connect(
979 functools.partial(self.__openLinkInNewTab, act))
980
981 from .WebBrowserLanguagesDialog import WebBrowserLanguagesDialog
982 languages = Preferences.toList(
983 Preferences.getSettings().value(
984 "WebBrowser/AcceptLanguages",
985 WebBrowserLanguagesDialog.defaultAcceptLanguages()))
986 if languages:
987 language = languages[0]
988 langCode = language.split("[")[1][:2]
989 googleTranslatorUrl = QUrl.fromEncoded(
990 b"http://translate.google.com/translate?sl=auto&tl=" +
991 langCode.encode() +
992 b"&u=" +
993 QUrl.toPercentEncoding(
994 bytes(self.url().toEncoded()).decode()))
995 act = menu.addAction(
996 UI.PixmapCache.getIcon("translate"),
997 self.tr("Google Translate"))
998 act.setData(googleTranslatorUrl)
999 act.triggered.connect(
1000 functools.partial(self.__openLinkInNewTab, act))
1001
1002 def __checkForForm(self, act, pos):
1003 """
1004 Private method to check the given position for an open search form.
1005
1006 @param act reference to the action to be populated upon success
1007 @type QAction
1008 @param pos position to be tested
1009 @type QPoint
1010 """
1011 self.__clickedPos = self.mapToViewport(pos)
1012
1013 from .Tools import Scripts
1014 script = Scripts.getFormData(self.__clickedPos)
1015 self.page().runJavaScript(
1016 script,
1017 WebBrowserPage.SafeJsWorld,
1018 lambda res: self.__checkForFormCallback(res, act))
1019
1020 def __checkForFormCallback(self, res, act):
1021 """
1022 Private method handling the __checkForForm result.
1023
1024 @param res result dictionary generated by JavaScript
1025 @type dict
1026 @param act reference to the action to be populated upon success
1027 @type QAction
1028 """
1029 if act is None or not bool(res):
1030 return
1031
1032 url = QUrl(res["action"])
1033 method = res["method"]
1034
1035 if not url.isEmpty() and method in ["get", "post"]:
1036 act.setVisible(True)
1037 act.setText(self.tr("Add to web search toolbar"))
1038 act.triggered.connect(self.__addSearchEngine)
1039
1040 def __isUrlValid(self, url):
1041 """
1042 Private method to check a URL for validity.
1043
1044 @param url URL to be checked (QUrl)
1045 @return flag indicating a valid URL (boolean)
1046 """
1047 return (
1048 url.isValid() and
1049 bool(url.host()) and
1050 bool(url.scheme()) and
1051 "." in url.host()
1052 )
1053
1054 def __replaceMisspelledWord(self, act):
1055 """
1056 Private slot to replace a misspelled word under the context menu.
1057
1058 @param act reference to the action that triggered
1059 @type QAction
1060 """
1061 suggestion = act.text()
1062 self.page().replaceMisspelledWord(suggestion)
1063
1064 def __openLinkInNewTab(self, act):
1065 """
1066 Private method called by the context menu to open a link in a new
1067 tab.
1068
1069 @param act reference to the action that triggered
1070 @type QAction
1071 """
1072 url = act.data()
1073 if url.isEmpty():
1074 return
1075
1076 self.setSource(url, newTab=True)
1077
1078 def __openLinkInNewWindow(self, act):
1079 """
1080 Private slot called by the context menu to open a link in a new
1081 window.
1082
1083 @param act reference to the action that triggered
1084 @type QAction
1085 """
1086 url = act.data()
1087 if url.isEmpty():
1088 return
1089
1090 self.__mw.newWindow(url)
1091
1092 def __openLinkInNewPrivateWindow(self, act):
1093 """
1094 Private slot called by the context menu to open a link in a new
1095 private window.
1096
1097 @param act reference to the action that triggered
1098 @type QAction
1099 """
1100 url = act.data()
1101 if url.isEmpty():
1102 return
1103
1104 self.__mw.newPrivateWindow(url)
1105
1106 def __bookmarkLink(self, act):
1107 """
1108 Private slot to bookmark a link via the context menu.
1109
1110 @param act reference to the action that triggered
1111 @type QAction
1112 """
1113 url = act.data()
1114 if url.isEmpty():
1115 return
1116
1117 from .Bookmarks.AddBookmarkDialog import AddBookmarkDialog
1118 dlg = AddBookmarkDialog()
1119 dlg.setUrl(bytes(url.toEncoded()).decode())
1120 dlg.exec()
1121
1122 def __sendLink(self, act):
1123 """
1124 Private slot to send a link via email.
1125
1126 @param act reference to the action that triggered
1127 @type QAction
1128 """
1129 data = act.data()
1130 if isinstance(data, QUrl) and data.isEmpty():
1131 return
1132
1133 if isinstance(data, QUrl):
1134 data = data.toString()
1135 QDesktopServices.openUrl(QUrl("mailto:?body=" + data))
1136
1137 def __copyLink(self, act):
1138 """
1139 Private slot to copy a link to the clipboard.
1140
1141 @param act reference to the action that triggered
1142 @type QAction
1143 """
1144 data = act.data()
1145 if isinstance(data, QUrl) and data.isEmpty():
1146 return
1147
1148 if isinstance(data, QUrl):
1149 data = data.toString()
1150
1151 # copy the URL to both clipboard areas
1152 QApplication.clipboard().setText(data, QClipboard.Mode.Clipboard)
1153 QApplication.clipboard().setText(data, QClipboard.Mode.Selection)
1154
1155 def __downloadLink(self):
1156 """
1157 Private slot to download a link and save it to disk.
1158 """
1159 self.triggerPageAction(QWebEnginePage.WebAction.DownloadLinkToDisk)
1160
1161 def __downloadImage(self):
1162 """
1163 Private slot to download an image and save it to disk.
1164 """
1165 self.triggerPageAction(QWebEnginePage.WebAction.DownloadImageToDisk)
1166
1167 def __copyImage(self):
1168 """
1169 Private slot to copy an image to the clipboard.
1170 """
1171 self.triggerPageAction(QWebEnginePage.WebAction.CopyImageToClipboard)
1172
1173 def __blockImage(self, act):
1174 """
1175 Private slot to add a block rule for an image URL.
1176
1177 @param act reference to the action that triggered
1178 @type QAction
1179 """
1180 url = act.data()
1181 dlg = WebBrowserWindow.adBlockManager().showDialog()
1182 dlg.addCustomRule(url)
1183
1184 def __searchImage(self, act):
1185 """
1186 Private slot to search for an image URL.
1187
1188 @param act reference to the action that triggered
1189 @type QAction
1190 """
1191 url = act.data()
1192 self.setSource(url, newTab=True)
1193
1194 def __downloadMedia(self):
1195 """
1196 Private slot to download a media and save it to disk.
1197 """
1198 self.triggerPageAction(QWebEnginePage.WebAction.DownloadMediaToDisk)
1199
1200 def __pauseMedia(self):
1201 """
1202 Private slot to pause or play the selected media.
1203 """
1204 self.triggerPageAction(QWebEnginePage.WebAction.ToggleMediaPlayPause)
1205
1206 def __muteMedia(self):
1207 """
1208 Private slot to (un)mute the selected media.
1209 """
1210 self.triggerPageAction(QWebEnginePage.WebAction.ToggleMediaMute)
1211
1212 def __virusTotal(self, act):
1213 """
1214 Private slot to scan the selected URL with VirusTotal.
1215
1216 @param act reference to the action that triggered
1217 @type QAction
1218 """
1219 url = act.data()
1220 self.__mw.requestVirusTotalScan(url)
1221
1222 def __searchDefaultRequested(self):
1223 """
1224 Private slot to search for some text with the current search engine.
1225 """
1226 searchText = self.selectedText()
1227
1228 if not searchText:
1229 return
1230
1231 engine = self.__mw.openSearchManager().currentEngine()
1232 if engine:
1233 self.search.emit(engine.searchUrl(searchText))
1234
1235 def __searchRequested(self, act):
1236 """
1237 Private slot to search for some text with a selected search engine.
1238
1239 @param act reference to the action that triggered this slot (QAction)
1240 """
1241 searchText = self.selectedText()
1242
1243 if not searchText:
1244 return
1245
1246 engineName = act.data()
1247 engine = (
1248 self.__mw.openSearchManager().engine(engineName)
1249 if engineName else
1250 self.__mw.openSearchManager().currentEngine()
1251 )
1252 if engine:
1253 self.search.emit(engine.searchUrl(searchText))
1254
1255 def __addSearchEngine(self):
1256 """
1257 Private slot to add a new search engine.
1258 """
1259 from .Tools import Scripts
1260 script = Scripts.getFormData(self.__clickedPos)
1261 self.page().runJavaScript(
1262 script,
1263 WebBrowserPage.SafeJsWorld,
1264 lambda res: self.__mw.openSearchManager().addEngineFromForm(
1265 res, self))
1266
1267 def __webInspector(self):
1268 """
1269 Private slot to show the web inspector window.
1270 """
1271 from .WebInspector import WebInspector
1272 if WebInspector.isEnabled():
1273 if self.__inspector is None:
1274 self.__inspector = WebInspector()
1275 self.__inspector.setView(self, True)
1276 self.__inspector.inspectorClosed.connect(
1277 self.closeWebInspector)
1278 self.__inspector.show()
1279 else:
1280 self.closeWebInspector()
1281
1282 def closeWebInspector(self):
1283 """
1284 Public slot to close the web inspector.
1285 """
1286 if self.__inspector is not None:
1287 if self.__inspector.isVisible():
1288 self.__inspector.hide()
1289 WebInspector.unregisterView(self.__inspector)
1290 self.__inspector.deleteLater()
1291 self.__inspector = None
1292
1293 def addBookmark(self):
1294 """
1295 Public slot to bookmark the current page.
1296 """
1297 from .Tools import Scripts
1298 script = Scripts.getAllMetaAttributes()
1299 self.page().runJavaScript(
1300 script, WebBrowserPage.SafeJsWorld, self.__addBookmarkCallback)
1301
1302 def __addBookmarkCallback(self, res):
1303 """
1304 Private callback method of __addBookmark().
1305
1306 @param res reference to the result list containing all
1307 meta attributes
1308 @type list
1309 """
1310 description = ""
1311 for meta in res:
1312 if meta["name"] == "description":
1313 description = meta["content"]
1314
1315 from .Bookmarks.AddBookmarkDialog import AddBookmarkDialog
1316 dlg = AddBookmarkDialog()
1317 dlg.setUrl(bytes(self.url().toEncoded()).decode())
1318 dlg.setTitle(self.title())
1319 dlg.setDescription(description)
1320 dlg.exec()
1321
1322 def dragEnterEvent(self, evt):
1323 """
1324 Protected method called by a drag enter event.
1325
1326 @param evt reference to the drag enter event (QDragEnterEvent)
1327 """
1328 evt.acceptProposedAction()
1329
1330 def dragMoveEvent(self, evt):
1331 """
1332 Protected method called by a drag move event.
1333
1334 @param evt reference to the drag move event (QDragMoveEvent)
1335 """
1336 evt.ignore()
1337 if evt.source() != self:
1338 if len(evt.mimeData().urls()) > 0:
1339 evt.acceptProposedAction()
1340 else:
1341 url = QUrl(evt.mimeData().text())
1342 if url.isValid():
1343 evt.acceptProposedAction()
1344
1345 if not evt.isAccepted():
1346 super().dragMoveEvent(evt)
1347
1348 def dropEvent(self, evt):
1349 """
1350 Protected method called by a drop event.
1351
1352 @param evt reference to the drop event (QDropEvent)
1353 """
1354 super().dropEvent(evt)
1355 if (
1356 not evt.isAccepted() and
1357 evt.source() != self and
1358 evt.possibleActions() & Qt.DropAction.CopyAction
1359 ):
1360 url = QUrl()
1361 if len(evt.mimeData().urls()) > 0:
1362 url = evt.mimeData().urls()[0]
1363 if not url.isValid():
1364 url = QUrl(evt.mimeData().text())
1365 if url.isValid():
1366 self.setSource(url)
1367 evt.acceptProposedAction()
1368
1369 def _mousePressEvent(self, evt):
1370 """
1371 Protected method called by a mouse press event.
1372
1373 @param evt reference to the mouse event (QMouseEvent)
1374 """
1375 if WebBrowserWindow.autoScroller().mousePress(self, evt):
1376 evt.accept()
1377 return
1378
1379 def _mouseReleaseEvent(self, evt):
1380 """
1381 Protected method called by a mouse release event.
1382
1383 @param evt reference to the mouse event (QMouseEvent)
1384 """
1385 if WebBrowserWindow.autoScroller().mouseRelease(evt):
1386 evt.accept()
1387 return
1388
1389 accepted = evt.isAccepted()
1390 self.__page.event(evt)
1391 if (
1392 not evt.isAccepted() and
1393 evt.button() == Qt.MouseButton.MiddleButton
1394 ):
1395 url = QUrl(QApplication.clipboard().text(
1396 QClipboard.Mode.Selection))
1397 if (
1398 not url.isEmpty() and
1399 url.isValid() and
1400 url.scheme() != ""
1401 ):
1402 self.setSource(url)
1403 evt.setAccepted(accepted)
1404
1405 def _mouseMoveEvent(self, evt):
1406 """
1407 Protected method to handle mouse move events.
1408
1409 @param evt reference to the mouse event (QMouseEvent)
1410 """
1411 if self.__mw and self.__mw.isFullScreen():
1412 if self.__mw.isFullScreenNavigationVisible():
1413 self.__mw.hideFullScreenNavigation()
1414 elif evt.y() < 10:
1415 # mouse is within 10px to the top
1416 self.__mw.showFullScreenNavigation()
1417
1418 if WebBrowserWindow.autoScroller().mouseMove(evt):
1419 evt.accept()
1420
1421 def _wheelEvent(self, evt):
1422 """
1423 Protected method to handle wheel events.
1424
1425 @param evt reference to the wheel event (QWheelEvent)
1426 """
1427 if WebBrowserWindow.autoScroller().wheel():
1428 evt.accept()
1429 return
1430
1431 delta = evt.angleDelta().y()
1432 if evt.modifiers() & Qt.KeyboardModifier.ControlModifier:
1433 if delta < 0:
1434 self.zoomOut()
1435 elif delta > 0:
1436 self.zoomIn()
1437 evt.accept()
1438
1439 elif evt.modifiers() & Qt.KeyboardModifier.ShiftModifier:
1440 if delta < 0:
1441 self.backward()
1442 elif delta > 0:
1443 self.forward()
1444 evt.accept()
1445
1446 def _keyPressEvent(self, evt):
1447 """
1448 Protected method called by a key press.
1449
1450 @param evt reference to the key event (QKeyEvent)
1451 """
1452 if self.__mw.personalInformationManager().viewKeyPressEvent(self, evt):
1453 evt.accept()
1454 return
1455
1456 if evt.key() == Qt.Key.Key_ZoomIn:
1457 self.zoomIn()
1458 evt.accept()
1459 elif evt.key() == Qt.Key.Key_ZoomOut:
1460 self.zoomOut()
1461 evt.accept()
1462 elif evt.key() == Qt.Key.Key_Plus:
1463 if evt.modifiers() & Qt.KeyboardModifier.ControlModifier:
1464 self.zoomIn()
1465 evt.accept()
1466 elif evt.key() == Qt.Key.Key_Minus:
1467 if evt.modifiers() & Qt.KeyboardModifier.ControlModifier:
1468 self.zoomOut()
1469 evt.accept()
1470 elif evt.key() == Qt.Key.Key_0:
1471 if evt.modifiers() & Qt.KeyboardModifier.ControlModifier:
1472 self.zoomReset()
1473 evt.accept()
1474 elif evt.key() == Qt.Key.Key_M:
1475 if evt.modifiers() & Qt.KeyboardModifier.ControlModifier:
1476 self.__muteMedia()
1477 evt.accept()
1478 elif evt.key() == Qt.Key.Key_Backspace:
1479 pos = QCursor.pos()
1480 pos = self.mapFromGlobal(pos)
1481 hitTest = self.page().hitTestContent(pos)
1482 if not hitTest.isContentEditable():
1483 self.pageAction(QWebEnginePage.WebAction.Back).trigger()
1484 evt.accept()
1485
1486 def _keyReleaseEvent(self, evt):
1487 """
1488 Protected method called by a key release.
1489
1490 @param evt reference to the key event (QKeyEvent)
1491 """
1492 if evt.key() == Qt.Key.Key_Escape and self.isFullScreen():
1493 self.triggerPageAction(QWebEnginePage.WebAction.ExitFullScreen)
1494 evt.accept()
1495 self.requestFullScreen(False)
1496
1497 def _gestureEvent(self, evt):
1498 """
1499 Protected method handling gesture events.
1500
1501 @param evt reference to the gesture event
1502 @type QGestureEvent
1503 """
1504 pinch = evt.gesture(Qt.GestureType.PinchGesture)
1505 if pinch:
1506 if pinch.state() == Qt.GestureState.GestureStarted:
1507 pinch.setTotalScaleFactor(self.__currentZoom / 100.0)
1508 elif pinch.state() == Qt.GestureState.GestureUpdated:
1509 scaleFactor = pinch.totalScaleFactor()
1510 self.setZoomValue(int(scaleFactor * 100))
1511 evt.accept()
1512
1513 def eventFilter(self, obj, evt):
1514 """
1515 Public method to process event for other objects.
1516
1517 @param obj reference to object to process events for
1518 @type QObject
1519 @param evt reference to event to be processed
1520 @type QEvent
1521 @return flag indicating that the event should be filtered out
1522 @rtype bool
1523 """
1524 if (
1525 obj is self and
1526 evt.type() == QEvent.Type.ParentChange and
1527 self.parentWidget() is not None
1528 ):
1529 self.parentWidget().installEventFilter(self)
1530
1531 # find the render widget receiving events for the web page
1532 if obj is self and evt.type() == QEvent.Type.ChildAdded:
1533 QTimer.singleShot(0, self.__setRwhvqt)
1534
1535 # forward events to WebBrowserView
1536 if (
1537 obj is self.__rwhvqt and
1538 evt.type() in [QEvent.Type.KeyPress,
1539 QEvent.Type.KeyRelease,
1540 QEvent.Type.MouseButtonPress,
1541 QEvent.Type.MouseButtonRelease,
1542 QEvent.Type.MouseMove,
1543 QEvent.Type.Wheel,
1544 QEvent.Type.Gesture]
1545 ):
1546 wasAccepted = evt.isAccepted()
1547 evt.setAccepted(False)
1548 if evt.type() == QEvent.Type.KeyPress:
1549 self._keyPressEvent(evt)
1550 elif evt.type() == QEvent.Type.KeyRelease:
1551 self._keyReleaseEvent(evt)
1552 elif evt.type() == QEvent.Type.MouseButtonPress:
1553 self._mousePressEvent(evt)
1554 elif evt.type() == QEvent.Type.MouseButtonRelease:
1555 self._mouseReleaseEvent(evt)
1556 elif evt.type() == QEvent.Type.MouseMove:
1557 self._mouseMoveEvent(evt)
1558 elif evt.type() == QEvent.Type.Wheel:
1559 self._wheelEvent(evt)
1560 elif evt.type() == QEvent.Type.Gesture:
1561 self._gestureEvent(evt)
1562 ret = evt.isAccepted()
1563 evt.setAccepted(wasAccepted)
1564 return ret
1565
1566 if (
1567 obj is self.parentWidget() and
1568 evt.type() in [QEvent.Type.KeyPress, QEvent.Type.KeyRelease]
1569 ):
1570 wasAccepted = evt.isAccepted()
1571 evt.setAccepted(False)
1572 if evt.type() == QEvent.Type.KeyPress:
1573 self._keyPressEvent(evt)
1574 elif evt.type() == QEvent.Type.KeyRelease:
1575 self._keyReleaseEvent(evt)
1576 ret = evt.isAccepted()
1577 evt.setAccepted(wasAccepted)
1578 return ret
1579
1580 # block already handled events
1581 if obj is self:
1582 if evt.type() in [QEvent.Type.KeyPress,
1583 QEvent.Type.KeyRelease,
1584 QEvent.Type.MouseButtonPress,
1585 QEvent.Type.MouseButtonRelease,
1586 QEvent.Type.MouseMove,
1587 QEvent.Type.Wheel,
1588 QEvent.Type.Gesture]:
1589 return True
1590
1591 elif evt.type() == QEvent.Type.Hide and self.isFullScreen():
1592 self.triggerPageAction(QWebEnginePage.WebAction.ExitFullScreen)
1593
1594 return super().eventFilter(obj, evt)
1595
1596 def event(self, evt):
1597 """
1598 Public method handling events.
1599
1600 @param evt reference to the event (QEvent)
1601 @return flag indicating, if the event was handled (boolean)
1602 """
1603 if evt.type() == QEvent.Type.Gesture:
1604 self._gestureEvent(evt)
1605 return True
1606
1607 return super().event(evt)
1608
1609 def inputWidget(self):
1610 """
1611 Public method to get a reference to the render widget.
1612
1613 @return reference to the render widget
1614 @rtype QWidget
1615 """
1616 return self.__rwhvqt
1617
1618 def clearHistory(self):
1619 """
1620 Public slot to clear the history.
1621 """
1622 self.history().clear()
1623 self.__urlChanged(self.history().currentItem().url())
1624
1625 ###########################################################################
1626 ## Signal converters below
1627 ###########################################################################
1628
1629 def __urlChanged(self, url):
1630 """
1631 Private slot to handle the urlChanged signal.
1632
1633 @param url the new url (QUrl)
1634 """
1635 self.sourceChanged.emit(url)
1636
1637 self.forwardAvailable.emit(self.isForwardAvailable())
1638 self.backwardAvailable.emit(self.isBackwardAvailable())
1639
1640 def __iconUrlChanged(self, url):
1641 """
1642 Private slot to handle the iconUrlChanged signal.
1643
1644 @param url URL to get web site icon from
1645 @type QUrl
1646 """
1647 self.__siteIcon = QIcon()
1648 if self.__siteIconLoader is not None:
1649 self.__siteIconLoader.deleteLater()
1650 self.__siteIconLoader = WebIconLoader(url, self)
1651 self.__siteIconLoader.iconLoaded.connect(self.__iconLoaded)
1652 with contextlib.suppress(AttributeError):
1653 self.__siteIconLoader.sslConfiguration.connect(
1654 self.page().setSslConfiguration)
1655 self.__siteIconLoader.clearSslConfiguration.connect(
1656 self.page().clearSslConfiguration)
1657
1658 def __iconLoaded(self, icon):
1659 """
1660 Private slot handling the loaded web site icon.
1661
1662 @param icon web site icon
1663 @type QIcon
1664 """
1665 self.__siteIcon = icon
1666
1667 from .Tools import WebIconProvider
1668 WebIconProvider.instance().saveIcon(self)
1669
1670 self.faviconChanged.emit()
1671
1672 def icon(self):
1673 """
1674 Public method to get the web site icon.
1675
1676 @return web site icon
1677 @rtype QIcon
1678 """
1679 if not self.__siteIcon.isNull():
1680 return QIcon(self.__siteIcon)
1681
1682 from .Tools import WebIconProvider
1683 return WebIconProvider.instance().iconForUrl(self.url())
1684
1685 def title(self):
1686 """
1687 Public method to get the view title.
1688
1689 @return view title
1690 @rtype str
1691 """
1692 titleStr = super().title()
1693 if not titleStr:
1694 if self.url().isEmpty():
1695 url = self.__page.requestedUrl()
1696 else:
1697 url = self.url()
1698
1699 titleStr = url.host()
1700 if not titleStr:
1701 titleStr = url.toString(
1702 QUrl.UrlFormattingOption.RemoveFragment)
1703
1704 if not titleStr or titleStr == "about:blank":
1705 titleStr = self.tr("Empty Page")
1706
1707 return titleStr
1708
1709 def __linkHovered(self, link):
1710 """
1711 Private slot to handle the linkHovered signal.
1712
1713 @param link the URL of the link (string)
1714 """
1715 self.highlighted.emit(link)
1716
1717 ###########################################################################
1718 ## Signal handlers below
1719 ###########################################################################
1720
1721 def __renderProcessTerminated(self, status, exitCode):
1722 """
1723 Private slot handling a crash of the web page render process.
1724
1725 @param status termination status
1726 @type QWebEnginePage.RenderProcessTerminationStatus
1727 @param exitCode exit code of the process
1728 @type int
1729 """
1730 if (
1731 status ==
1732 QWebEnginePage.RenderProcessTerminationStatus
1733 .NormalTerminationStatus
1734 ):
1735 return
1736
1737 QTimer.singleShot(0,
1738 functools.partial(self.__showTabCrashPage, status))
1739
1740 def __showTabCrashPage(self, status):
1741 """
1742 Private slot to show the tab crash page.
1743
1744 @param status termination status
1745 @type QWebEnginePage.RenderProcessTerminationStatus
1746 """
1747 self.page().deleteLater()
1748 self.__createNewPage()
1749
1750 html = getHtmlPage("tabCrashPage.html")
1751 html = html.replace("@IMAGE@", pixmapToDataUrl(
1752 ericApp().style().standardIcon(
1753 QStyle.StandardPixmap.SP_MessageBoxWarning).pixmap(48, 48)
1754 ).toString())
1755 html = html.replace("@FAVICON@", pixmapToDataUrl(
1756 ericApp().style() .standardIcon(
1757 QStyle.StandardPixmap.SP_MessageBoxWarning).pixmap(16, 16)
1758 ).toString())
1759 html = html.replace(
1760 "@TITLE@", self.tr("Render Process terminated abnormally"))
1761 html = html.replace(
1762 "@H1@", self.tr("Render Process terminated abnormally"))
1763 if (
1764 status ==
1765 QWebEnginePage.RenderProcessTerminationStatus
1766 .CrashedTerminationStatus
1767 ):
1768 msg = self.tr("The render process crashed while"
1769 " loading this page.")
1770 elif (
1771 status ==
1772 QWebEnginePage.RenderProcessTerminationStatus
1773 .KilledTerminationStatus
1774 ):
1775 msg = self.tr("The render process was killed.")
1776 else:
1777 msg = self.tr("The render process terminated while"
1778 " loading this page.")
1779 html = html.replace("@LI-1@", msg)
1780 html = html.replace(
1781 "@LI-2@",
1782 self.tr(
1783 "Try reloading the page or closing some tabs to make more"
1784 " memory available."))
1785 self.page().setHtml(html, self.url())
1786
1787 def __loadStarted(self):
1788 """
1789 Private method to handle the loadStarted signal.
1790 """
1791 # reset search
1792 self.findText("")
1793 self.__isLoading = True
1794 self.__progress = 0
1795
1796 def __loadProgress(self, progress):
1797 """
1798 Private method to handle the loadProgress signal.
1799
1800 @param progress progress value (integer)
1801 """
1802 self.__progress = progress
1803
1804 def __loadFinished(self, ok):
1805 """
1806 Private method to handle the loadFinished signal.
1807
1808 @param ok flag indicating the result (boolean)
1809 """
1810 self.__isLoading = False
1811 self.__progress = 0
1812
1813 QApplication.processEvents()
1814 QTimer.singleShot(200, self.__renderPreview)
1815
1816 from .ZoomManager import ZoomManager
1817 zoomValue = ZoomManager.instance().zoomValue(self.url())
1818 self.setZoomValue(zoomValue)
1819
1820 if ok:
1821 self.__mw.historyManager().addHistoryEntry(self)
1822 self.__mw.adBlockManager().page().hideBlockedPageEntries(
1823 self.page())
1824 self.__mw.passwordManager().completePage(self.page())
1825
1826 self.page().runJavaScript(
1827 "document.lastModified", WebBrowserPage.SafeJsWorld,
1828 lambda res: self.__adjustBookmark(res))
1829
1830 def __adjustBookmark(self, lastModified):
1831 """
1832 Private slot to adjust the 'lastModified' value of bookmarks.
1833
1834 @param lastModified last modified value
1835 @type str
1836 """
1837 modified = QDateTime.fromString(lastModified, "MM/dd/yyyy hh:mm:ss")
1838 if modified.isValid():
1839 from WebBrowser.WebBrowserWindow import WebBrowserWindow
1840 from .Bookmarks.BookmarkNode import BookmarkNode
1841 manager = WebBrowserWindow.bookmarksManager()
1842 for bookmark in manager.bookmarksForUrl(self.url()):
1843 manager.setTimestamp(bookmark, BookmarkNode.TsModified,
1844 modified)
1845
1846 def isLoading(self):
1847 """
1848 Public method to get the loading state.
1849
1850 @return flag indicating the loading state (boolean)
1851 """
1852 return self.__isLoading
1853
1854 def progress(self):
1855 """
1856 Public method to get the load progress.
1857
1858 @return load progress (integer)
1859 """
1860 return self.__progress
1861
1862 def __renderPreview(self):
1863 """
1864 Private slot to render a preview pixmap after the page was loaded.
1865 """
1866 from .WebBrowserSnap import renderTabPreview
1867 w = 600 # some default width, the preview gets scaled when shown
1868 h = int(w * self.height() / self.width())
1869 self.__preview = renderTabPreview(self, w, h)
1870
1871 def getPreview(self):
1872 """
1873 Public method to get the preview pixmap.
1874
1875 @return preview pixmap
1876 @rtype QPixmap
1877 """
1878 return self.__preview
1879
1880 def saveAs(self):
1881 """
1882 Public method to save the current page to a file.
1883 """
1884 url = self.url()
1885 if url.isEmpty():
1886 return
1887
1888 fileName, savePageFormat = self.__getSavePageFileNameAndFormat()
1889 if fileName:
1890 self.page().save(fileName, savePageFormat)
1891
1892 def __getSavePageFileNameAndFormat(self):
1893 """
1894 Private method to get the file name to save the page to.
1895
1896 @return tuple containing the file name to save to and the
1897 save page format
1898 @rtype tuple of (str, QWebEngineDownloadRequest.SavePageFormat)
1899 """
1900 documentLocation = QStandardPaths.writableLocation(
1901 QStandardPaths.StandardLocation.DocumentsLocation)
1902 filterList = [
1903 self.tr("Web Archive (*.mhtml *.mht)"),
1904 self.tr("HTML File (*.html *.htm)"),
1905 self.tr("HTML File with all resources (*.html *.htm)"),
1906 ]
1907 extensionsList = [
1908 # tuple of extensions for *nix and Windows
1909 # keep in sync with filters list
1910 (".mhtml", ".mht"),
1911 (".html", ".htm"),
1912 (".html", ".htm"),
1913 ]
1914 if self.url().fileName():
1915 defaultFileName = os.path.join(documentLocation,
1916 self.url().fileName())
1917 else:
1918 defaultFileName = os.path.join(documentLocation,
1919 self.page().title())
1920 if Utilities.isWindowsPlatform():
1921 defaultFileName += ".mht"
1922 else:
1923 defaultFileName += ".mhtml"
1924
1925 fileName = ""
1926 saveFormat = (
1927 QWebEngineDownloadRequest.SavePageFormat.MimeHtmlSaveFormat
1928 )
1929
1930 fileName, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
1931 None,
1932 self.tr("Save Web Page"),
1933 defaultFileName,
1934 ";;".join(filterList),
1935 None)
1936 if fileName:
1937 index = filterList.index(selectedFilter)
1938 if index == 0:
1939 saveFormat = (
1940 QWebEngineDownloadRequest.SavePageFormat.MimeHtmlSaveFormat
1941 )
1942 elif index == 1:
1943 saveFormat = (
1944 QWebEngineDownloadRequest.SavePageFormat
1945 .SingleHtmlSaveFormat
1946 )
1947 else:
1948 saveFormat = (
1949 QWebEngineDownloadRequest.SavePageFormat
1950 .CompleteHtmlSaveFormat
1951 )
1952
1953 extension = os.path.splitext(fileName)[1]
1954 if not extension:
1955 # add the platform specific default extension
1956 if Utilities.isWindowsPlatform():
1957 extensionsIndex = 1
1958 else:
1959 extensionsIndex = 0
1960 extensions = extensionsList[index]
1961 fileName += extensions[extensionsIndex]
1962
1963 return fileName, saveFormat
1964
1965 ###########################################################################
1966 ## Miscellaneous methods below
1967 ###########################################################################
1968
1969 def createWindow(self, windowType):
1970 """
1971 Public method called, when a new window should be created.
1972
1973 @param windowType type of the requested window
1974 (QWebEnginePage.WebWindowType)
1975 @return reference to the created browser window (WebBrowserView)
1976 """
1977 if windowType in [QWebEnginePage.WebWindowType.WebBrowserTab,
1978 QWebEnginePage.WebWindowType.WebDialog]:
1979 return self.__mw.newTab(addNextTo=self)
1980 elif windowType == QWebEnginePage.WebWindowType.WebBrowserWindow:
1981 return self.__mw.newWindow().currentBrowser()
1982 elif (
1983 windowType == QWebEnginePage.WebWindowType.WebBrowserBackgroundTab
1984 ):
1985 return self.__mw.newTab(addNextTo=self, background=True)
1986 else:
1987 # default for unknow/new window types
1988 return self.__mw.newTab(addNextTo=self)
1989
1990 def preferencesChanged(self):
1991 """
1992 Public method to indicate a change of the settings.
1993 """
1994 self.reload()
1995
1996 ###########################################################################
1997 ## RSS related methods below
1998 ###########################################################################
1999
2000 def checkRSS(self):
2001 """
2002 Public method to check, if the loaded page contains feed links.
2003
2004 @return flag indicating the existence of feed links (boolean)
2005 """
2006 self.__rss = []
2007
2008 script = Scripts.getFeedLinks()
2009 feeds = self.page().execJavaScript(script)
2010
2011 if feeds is not None:
2012 for feed in feeds:
2013 if feed["url"] and feed["title"]:
2014 self.__rss.append((feed["title"], feed["url"]))
2015
2016 return len(self.__rss) > 0
2017
2018 def getRSS(self):
2019 """
2020 Public method to get the extracted RSS feeds.
2021
2022 @return list of RSS feeds (list of tuples of two strings)
2023 """
2024 return self.__rss
2025
2026 def hasRSS(self):
2027 """
2028 Public method to check, if the loaded page has RSS links.
2029
2030 @return flag indicating the presence of RSS links (boolean)
2031 """
2032 return len(self.__rss) > 0
2033
2034 ###########################################################################
2035 ## Full Screen handling below
2036 ###########################################################################
2037
2038 def isFullScreen(self):
2039 """
2040 Public method to check, if full screen mode is active.
2041
2042 @return flag indicating full screen mode
2043 @rtype bool
2044 """
2045 return self.__mw.isFullScreen()
2046
2047 def requestFullScreen(self, enable):
2048 """
2049 Public method to request full screen mode.
2050
2051 @param enable flag indicating full screen mode on or off
2052 @type bool
2053 """
2054 if enable:
2055 self.__mw.enterHtmlFullScreen()
2056 else:
2057 self.__mw.showNormal()
2058
2059 ###########################################################################
2060 ## Speed Dial slots below
2061 ###########################################################################
2062
2063 def __addSpeedDial(self):
2064 """
2065 Private slot to add a new speed dial.
2066 """
2067 self.__page.runJavaScript("addSpeedDial();",
2068 WebBrowserPage.SafeJsWorld)
2069
2070 def __configureSpeedDial(self):
2071 """
2072 Private slot to configure the speed dial.
2073 """
2074 self.page().runJavaScript("configureSpeedDial();",
2075 WebBrowserPage.SafeJsWorld)
2076
2077 def __reloadAllSpeedDials(self):
2078 """
2079 Private slot to reload all speed dials.
2080 """
2081 self.page().runJavaScript("reloadAll();", WebBrowserPage.SafeJsWorld)
2082
2083 def __resetSpeedDials(self):
2084 """
2085 Private slot to reset all speed dials to the default pages.
2086 """
2087 self.__speedDial.resetDials()
2088
2089 ###########################################################################
2090 ## Methods below implement session related functions
2091 ###########################################################################
2092
2093 def storeSessionData(self, data):
2094 """
2095 Public method to store session data to be restored later on.
2096
2097 @param data dictionary with session data to be restored
2098 @type dict
2099 """
2100 self.__restoreData = data
2101
2102 def __showEventSlot(self):
2103 """
2104 Private slot to perform actions when the view is shown and the event
2105 loop is running.
2106 """
2107 if self.__restoreData:
2108 sessionData, self.__restoreData = self.__restoreData, None
2109 self.loadFromSessionData(sessionData)
2110
2111 def showEvent(self, evt):
2112 """
2113 Protected method to handle show events.
2114
2115 @param evt reference to the show event object
2116 @type QShowEvent
2117 """
2118 super().showEvent(evt)
2119 self.activateSession()
2120
2121 def activateSession(self):
2122 """
2123 Public slot to activate a restored session.
2124 """
2125 if self.__restoreData and not self.__mw.isClosing():
2126 QTimer.singleShot(0, self.__showEventSlot)
2127
2128 def getSessionData(self):
2129 """
2130 Public method to populate the session data.
2131
2132 @return dictionary containing the session data
2133 @rtype dict
2134 """
2135 if self.__restoreData:
2136 # page has not been shown yet
2137 return self.__restoreData
2138
2139 sessionData = {}
2140 page = self.page()
2141
2142 # 1. zoom factor
2143 sessionData["ZoomFactor"] = page.zoomFactor()
2144
2145 # 2. scroll position
2146 scrollPos = page.scrollPosition()
2147 sessionData["ScrollPosition"] = {
2148 "x": scrollPos.x(),
2149 "y": scrollPos.y(),
2150 }
2151
2152 # 3. page history
2153 historyArray = QByteArray()
2154 stream = QDataStream(historyArray, QIODevice.OpenModeFlag.WriteOnly)
2155 stream << page.history()
2156 sessionData["History"] = str(
2157 historyArray.toBase64(QByteArray.Base64Option.Base64UrlEncoding),
2158 encoding="ascii")
2159 sessionData["HistoryIndex"] = page.history().currentItemIndex()
2160
2161 # 4. current URL and title
2162 sessionData["Url"] = self.url().toString()
2163 sessionData["Title"] = self.title()
2164
2165 # 5. web icon
2166 iconArray = QByteArray()
2167 stream = QDataStream(iconArray, QIODevice.OpenModeFlag.WriteOnly)
2168 stream << page.icon()
2169 sessionData["Icon"] = str(iconArray.toBase64(), encoding="ascii")
2170
2171 return sessionData
2172
2173 def loadFromSessionData(self, sessionData):
2174 """
2175 Public method to load the session data.
2176
2177 @param sessionData dictionary containing the session data as
2178 generated by getSessionData()
2179 @type dict
2180 """
2181 page = self.page()
2182 # blank the page
2183 page.setUrl(QUrl("about:blank"))
2184
2185 # 1. page history
2186 if "History" in sessionData:
2187 historyArray = QByteArray.fromBase64(
2188 sessionData["History"].encode("ascii"),
2189 QByteArray.Base64Option.Base64UrlEncoding)
2190 stream = QDataStream(historyArray, QIODevice.OpenModeFlag.ReadOnly)
2191 stream >> page.history()
2192
2193 if "HistoryIndex" in sessionData:
2194 item = page.history().itemAt(sessionData["HistoryIndex"])
2195 if item is not None:
2196 page.history().goToItem(item)
2197
2198 # 2. zoom factor
2199 if "ZoomFactor" in sessionData:
2200 page.setZoomFactor(sessionData["ZoomFactor"])
2201
2202 # 3. scroll position
2203 if "ScrollPosition" in sessionData:
2204 scrollPos = sessionData["ScrollPosition"]
2205 page.scrollTo(QPointF(scrollPos["x"], scrollPos["y"]))
2206
2207 def extractSessionMetaData(self, sessionData):
2208 """
2209 Public method to extract some session meta data elements needed by the
2210 tab widget in case of deferred loading.
2211
2212 @param sessionData dictionary containing the session data as
2213 generated by getSessionData()
2214 @type dict
2215 @return tuple containing the title, URL and web icon
2216 @rtype tuple of (str, str, QIcon)
2217 """
2218 title = sessionData.get("Title", "")
2219 urlStr = sessionData.get("Url", "")
2220
2221 if "Icon" in sessionData:
2222 iconArray = QByteArray.fromBase64(
2223 sessionData["Icon"].encode("ascii"))
2224 stream = QDataStream(iconArray, QIODevice.OpenModeFlag.ReadOnly)
2225 icon = QIcon()
2226 stream >> icon
2227 else:
2228 from .Tools import WebIconProvider
2229 icon = WebIconProvider.instance().iconForUrl(
2230 QUrl.fromUserInput(urlStr))
2231
2232 return title, urlStr, icon
2233
2234 ###########################################################################
2235 ## Methods below implement safe browsing related functions
2236 ###########################################################################
2237
2238 def getSafeBrowsingStatus(self):
2239 """
2240 Public method to get the safe browsing status of the current page.
2241
2242 @return flag indicating a safe site
2243 @rtype bool
2244 """
2245 if self.__page:
2246 return self.__page.getSafeBrowsingStatus()
2247 else:
2248 return True
2249
2250 ###########################################################################
2251 ## Methods below implement print support from the page
2252 ###########################################################################
2253
2254 @pyqtSlot()
2255 def __printPage(self):
2256 """
2257 Private slot to support printing from the web page.
2258 """
2259 self.__mw.tabWidget.printBrowser(browser=self)
2260
2261 ###########################################################################
2262 ## Methods below implement slots for Qt 5.11+
2263 ###########################################################################
2264
2265 @pyqtSlot("QWebEngineQuotaRequest")
2266 def __quotaRequested(self, quotaRequest):
2267 """
2268 Private slot to handle quota requests of the web page.
2269
2270 @param quotaRequest reference to the quota request object
2271 @type QWebEngineQuotaRequest
2272 """
2273 acceptRequest = Preferences.getWebBrowser("AcceptQuotaRequest")
2274 # map yes/no/ask from (0, 1, 2)
2275 if acceptRequest == 0:
2276 # always yes
2277 ok = True
2278 elif acceptRequest == 1:
2279 # always no
2280 ok = False
2281 else:
2282 # ask user
2283 from .Download.DownloadUtilities import dataString
2284 sizeStr = dataString(quotaRequest.requestedSize())
2285
2286 ok = EricMessageBox.yesNo(
2287 self,
2288 self.tr("Quota Request"),
2289 self.tr("""<p> Allow the website at <b>{0}</b> to use"""
2290 """ <b>{1}</b> of persistent storage?</p>""")
2291 .format(quotaRequest.origin().host(), sizeStr)
2292 )
2293
2294 if ok:
2295 quotaRequest.accept()
2296 else:
2297 quotaRequest.reject()
2298
2299 ###########################################################################
2300 ## Methods below implement slots for Qt 5.12+
2301 ###########################################################################
2302
2303 @pyqtSlot("QWebEngineClientCertificateSelection")
2304 def __selectClientCertificate(self, clientCertificateSelection):
2305 """
2306 Private slot to handle the client certificate selection request.
2307
2308 @param clientCertificateSelection list of client SSL certificates
2309 found in system's client certificate store
2310 @type QWebEngineClientCertificateSelection
2311 """
2312 certificates = clientCertificateSelection.certificates()
2313 if len(certificates) == 0:
2314 clientCertificateSelection.selectNone()
2315 elif len(certificates) == 1:
2316 clientCertificateSelection.select(certificates[0])
2317 else:
2318 certificate = None
2319 from EricNetwork.EricSslCertificateSelectionDialog import (
2320 EricSslCertificateSelectionDialog
2321 )
2322 dlg = EricSslCertificateSelectionDialog(certificates, self)
2323 if dlg.exec() == QDialog.DialogCode.Accepted:
2324 certificate = dlg.getSelectedCertificate()
2325
2326 if certificate is None:
2327 clientCertificateSelection.selectNone()
2328 else:
2329 clientCertificateSelection.select(certificate)

eric ide

mercurial