import os
from functools import lru_cache as cache
from JAK.Utils import check_url_rules, get_current_path, bindings
from JAK.Widgets import Dialog
from JAK.RequestInterceptor import Interceptor
if bindings() == "PyQt5":
from PyQt5.QtCore import QUrl, Qt
from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEnginePage, QWebEngineSettings
else:
from PySide2.QtCore import QUrl, Qt
from PySide2.QtWebEngineCore import QWebEngineUrlSchemeHandler
from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEnginePage, QWebEngineSettings@cache(maxsize=5)
def validate_url(self, url: str) -> None: if "!doctype" in url.lower():Inject HTML
base_url = get_current_path()
self.setHtml(url, QUrl(f"file://{base_url}/"))
print("Loading local HTML")
else:
if url.endswith(".html"):HTML file
if not url.startswith("/"):
url = f"/{url}"
url = f"file://{url}"
elif "://" not in url:HTML URL
url = f"https://{url}"
url = QUrl(url)
if url.isValid():
self.load(url)
print(f"Loading URL:{url.toString()}")class IpcSchemeHandler(QWebEngineUrlSchemeHandler): def __init__(self):
super().__init__() def requestStarted(self, request):
url = request.requestUrl().toString()
if url.startswith("ipc:"): from JAK.IPC import Communication
Communication.send(url)
returnclass JWebPage(QWebEnginePage): def __init__(self, profile, webview, config):
self.config = config
super(JWebPage, self).__init__(profile, webview)
self.featurePermissionRequested.connect(self._on_feature_permission_requested)Open url in a external browser
def _open_in_browser(self) -> None: print("Open above^ tab in Browser")
from webbrowser import open_new_tab
open_new_tab(self.url)Opens a dialog to confirm if user wants to open url in external browser
def _dialog_open_in_browser(self) -> None: msg = "Open In Your Browser"
Dialog.question(self.parent(), self.title(), msg, self._open_in_browser) @cache(maxsize=10)
def acceptNavigationRequest(self, url, _type, is_main_frame) -> bool: self.url = url.toString()
self.page = JWebPage(self.profile(), self.view(), self.config)Redirect new tabs to same window
self.page.urlChanged.connect(self._on_url_changed)
if self.config['webview']["online"]:
if _type == QWebEnginePage.WebWindowType.WebBrowserTab:
if self.config['webview']["urlRules"]:Check for URL rules on new tabs
if self.url.startswith(self.config['webview']["urlRules"]["WebBrowserTab"]):
self.open_window(self.url)
return False
elif check_url_rules("WebBrowserTab", self.url, self.config['webview']["urlRules"]):
print(f"Redirecting WebBrowserTab^ to same window")
return True
else:
print(f"Deny WebBrowserTab:{self.url}")check against WebBrowserWindow list to avoid duplicate dialogs
if not check_url_rules("WebBrowserWindow", self.url, self.config['webview']["urlRules"]):
self._dialog_open_in_browser()
return False
else:
return True
elif _type == QWebEnginePage.WebBrowserBackgroundTab:
print(f"WebBrowserBackgroundTab request:{self.url}")
return True
elif _type == QWebEnginePage.WebBrowserWindow:
if self.config['webview']["urlRules"] and self.config['webview']["online"]:Check URL rules on new windows
if check_url_rules("WebBrowserWindow", self.url, self.config['webview']["urlRules"]):
print(f"Deny WebBrowserWindow:{self.url}")
self._dialog_open_in_browser()
return False
else:
print(f"Allow WebBrowserWindow:{self.url}")
return True
else:
return True
elif _type == QWebEnginePage.WebDialog:
return True
return True def _on_feature_permission_requested(self, security_origin, feature): def grant_permission():
self.setFeaturePermission(security_origin, feature, self.PermissionGrantedByUser) def deny_permission():
self.setFeaturePermission(security_origin, feature, self.PermissionDeniedByUser)
if feature == self.Notifications:
grant_permission()
elif feature == self.MediaAudioVideoCapture and self.config['webview']["MediaAudioVideoCapture"]:
grant_permission()
elif feature == self.MediaVideoCapture and self.config['webview']["MediaVideoCapture"]:
grant_permission()
elif feature == self.MediaAudioCapture and self.config['webview']["MediaAudioCapture"]:
grant_permission()
elif feature == self.Geolocation and self.config['webview']["Geolocation"]:
grant_permission()
elif feature == self.MouseLock and self.config['webview']["MouseLock"]:
grant_permission()
elif feature == self.DesktopVideoCapture and self.config['webview']["DesktopVideoCapture"]:
grant_permission()
elif feature == self.DesktopAudioVideoCapture and self.config['webview']["DesktopAudioVideoCapture"]:
grant_permission()
else:
deny_permission()Open a New Window
def open_window(self, url):FIXME cookies path needs to be declared for this to work
self.popup = JWebView(self.config)
self.popup.page().windowCloseRequested.connect(self.popup.close)
self.popup.show()
print(f"Opening New Window^") @cache(maxsize=2)
def createWindow(self, _type: object) -> QWebEnginePage: return self.page def _on_url_changed(self, url: str) -> None:
url = url.toString()
if url == "about:blank":
return False
else:
validate_url(self, url)class JWebView(QWebEngineView): def __init__(self, config):
self.config = config
super(JWebView, self).__init__()
self.setAttribute(Qt.WA_DeleteOnClose, True)
self.profile = QWebEngineProfile.defaultProfile()
self.webpage = JWebPage(self.profile, self, config)
self.setPage(self.webpage)
if config['webview']["injectJavaScript"]["JavaScript"]:
self._inject_script(config['webview']["injectJavaScript"])
self.interceptor = Interceptor(config)
if config['webview']["userAgent"]:Set user agent
self.profile.setHttpUserAgent(config['webview']["userAgent"])
if config["debug"]:
self.settings().setAttribute(QWebEngineSettings.XSSAuditingEnabled, True)
else:
self.setContextMenuPolicy(Qt.PreventContextMenu)
if config['window']["transparent"]:Activates background transparency
self.setAttribute(Qt.WA_TranslucentBackground)
self.page().setBackgroundColor(Qt.transparent)
print("Transparency detected") self.settings().setAttribute(self.config['webview']['disabledSettings'], False)
for setting in self.config['webview']['enabledSettings']:
self.settings().setAttribute(setting, True)
if config['webview']["online"]:
self.settings().setAttribute(QWebEngineSettings.DnsPrefetchEnabled, True)
print("Engine online IPC and Bridge Disabled")
self.page().profile().downloadRequested.connect(self._download_requested)Set persistent cookies
self.profile.setPersistentCookiesPolicy(QWebEngineProfile.ForcePersistentCookies)set cookies on user folder
if config['webview']["cookiesPath"]:allow specific path per application.
_cookies_path = f"{os.getenv('HOME')}/.jak/{config['webview']['cookiesPath']}"
else:use separate cookies database per application
title = config['window']["title"].lower().replace(" ", "-")
_cookies_path = f"{os.getenv('HOME')}/.jak/{title}"
self.profile.setPersistentStoragePath(_cookies_path)
print(f"Cookies PATH:{_cookies_path}")
else:
self.settings().setAttribute(QWebEngineSettings.ShowScrollBars, False)
application_script = "const JAK = {};"
if config['webview']["IPC"]:
print("IPC Active:")
self._ipc_scheme_handler = IpcSchemeHandler()
self.profile.installUrlSchemeHandler('ipc'.encode(), self._ipc_scheme_handler)
application_script += """JAK.IPC = function(backendFunction) {
window.location.href = "ipc:" + backendFunction;
};"""
if config['webview']["webChannel"]["active"]:
if bindings() == "PyQt5":
from PyQt5.QtCore import QFile, QIODevice
from PyQt5.QtWebChannel import QWebChannel
webchannel_js = QFile(':/qtwebchannel/qwebchannel.js')
webchannel_js.open(QIODevice.ReadOnly)
webchannel_js = bytes(webchannel_js.readAll()).decode('utf-8')
webchannel_js += """new QWebChannel(qt.webChannelTransport, function (channel) {
JAK.Bridge = channel.objects.Bridge;
});"""
application_script += webchannel_js
self._inject_script({"JavaScript":application_script, "name":"JAK"})
channel = QWebChannel(self.page())
if config['webview']["webChannel"]["sharedOBJ"]:
bridge_obj = config['webview']["webChannel"]["sharedOBJ"]
else:
raise NotImplementedError("QWebChannel shared QObject")
channel.registerObject("Bridge", bridge_obj)
self.page().setWebChannel(channel)
print("WebChannel Active:")
else:
self._inject_script({"JavaScript":application_script, "name":"JAK"})
self.profile.setRequestInterceptor(self.interceptor)
print(self.profile.httpUserAgent())
validate_url(self, config['webview']["webContents"]) def _inject_script(self, script: dict):
from JAK.Utils import JavaScript
JavaScript.inject(self.page(), script) def dropEvent(self, *args):disable drop event
pass def _download_requested(self, download_item) -> None: if bindings() == "PyQt5":
from PyQt5.QtWidgets import QFileDialog
else:
from PySide2.QtWidgets import QFileDialog
dialog = QFileDialog(self)
path = dialog.getSaveFileName(dialog, "Save File", download_item.path())
if path[0]:
download_item.setPath(path[0])
print(f"downloading file to:( {download_item.path()} )")
download_item.accept()
self.download_item = download_item
download_item.finished.connect(self._download_finished)
else:
print("Download canceled")Goes to previous page and pops an alert informing the user that the download is finish and were to find it
def _download_finished(self) -> None: file_path = self.download_item.path()
msg = f"File Downloaded to: {file_path}"
Dialog.information(self, "Download Complete", msg)