toga icon indicating copy to clipboard operation
toga copied to clipboard

Provide a way for Javascript code in a Webview to invoke Python functions

Open appleman4000 opened this issue 2 years ago • 12 comments

What is the problem or limitation you are having?

It is possible for Python to invoke Javascript code in a web view; however, it is not possible for a Javascript function to call back to Python code.

Describe the solution you'd like

PyQt5 WebView not work in iOS or Android so I hope toga can realize this function

this is pyqt5 solution

index.html:

<!DOCTYPE html>
<html>
<head>
    <title>Webview Demo</title>
    <script type="text/javascript">
        function callPython() {
            alert(api.print('print from JavaScript!'));
        }
    </script>
</head>
<body>
    <button onclick="callPython()">Call Python Code print</button>
</body>
</html>

webview.py:

import os

from PyQt5.QtCore import QUrl
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
from PyQt5.QtWidgets import QWidget

from posmesh.platform import API


class WebPage(QWebPage):
    def __init__(self, parent=None):
        super(WebPage, self).__init__(parent)
        self.api = API()
	self.mainFrame().javaScriptWindowObjectCleared.connect(self.populateJavaScriptWindowObject)
    def populateJavaScriptWindowObject(self):
        self.mainFrame().addToJavaScriptWindowObject('api', self.api)
		
class Webview(QWidget):
    def __init__(self, url="", parent=None):
        super(Webview, self).__init__(parent)
        self.view = QWebView(self)
        self.view.setPage(WebPage())
        self.view.settings().setAttribute(QWebSettings.LocalContentCanAccessRemoteUrls, True)
        self.view.load(QUrl.fromLocalFile(os.getcwd() + url))  
        self.view.show()

api.py

from PyQt5.QtCore import pyqtSlot as Slot, QObject
class API(QObject):
    @Slot(str, result=str)
    def print(self, msg):
        print(msg)
        return 'printed'

Describe alternatives you've considered

Haven't found a WebView jsbridge solution across iOS, Android, windows, and Linux yet using python.

Additional context

No response

appleman4000 avatar Dec 10 '23 13:12 appleman4000

Agreed this would be useful mechanism to have in Toga's WebView API. If nothing else, it would be necessary for a full "Electron"-style app shell replacement, so that web code can invoke native system functions.

The implementation is going to be very platform specific. From a quick poke around, here are some pointers on the APIs that will be required:

Ultimately, we need to be able to have a webview.register_handler("name", handler_method) API of some kind; however, in the meantime, you can use the platform specific APIs by dropping to the native interface. On any user-space toga.WebView instance, webview._impl.native will be the platform native WKWebKit/GTK.WebView2/etc instance; it should be possible to use the example code provided above on this native object.

freakboy3742 avatar Dec 10 '23 22:12 freakboy3742

FWIW, this can be currently done with: https://github.com/python-eel/Eel. It can be combined with toga's webview for calling python from js.

proneon267 avatar Jan 11 '24 08:01 proneon267

@proneon267 Are you suggesting that as something someone could drop in to a Toga app? Or an example of someone else implementing the equivalent functionality (using the APIs that I highlighted in my previous post)?

freakboy3742 avatar Jan 11 '24 23:01 freakboy3742

Yes, someone can just use it in toga as is. I had tested the (toga+python-eel combo) and it had worked properly on desktop and mobile platform like Android.

proneon267 avatar Jan 11 '24 23:01 proneon267

That... seems very unusual to me. Looking at the code, it only supports Chrome and Edge - so it can't support Toga's macOS interface - and it doesn't include any code differentiating Android from Linux; and it doesn't include any hooks into the native platform webview APIs. Can you provide an example of how it is used?

freakboy3742 avatar Jan 12 '24 00:01 freakboy3742

Sure. I'll find the old project I was working on and get back to you.

proneon267 avatar Jan 12 '24 01:01 proneon267

@freakboy3742 I have gutted out the complex logic in my old project and replaced it with a simpler logic. Here is the example briefcase project: https://github.com/proneon267/toga-python-eel.

Particularly, take a look at: app.py: https://github.com/proneon267/toga-python-eel/blob/main/togapythoneel/src/togapythoneel/app.py and script.js: https://github.com/proneon267/toga-python-eel/blob/main/togapythoneel/src/togapythoneel/web/script.js

I have tested the example on Windows, Android and MacOS. Everything seems to be working properly.

I also tried to run it on iOS but got an error because, gevent is not in the Anaconda iOS repository:https://anaconda.org/beeware/repo. So, it seems it is just a matter of building the binary wheels for gevent.

Looks like we are closer to cross-platform Electron-Style apps in Python :)

proneon267 avatar Jan 13 '24 17:01 proneon267

Ah - you're not using Eel's core functionality.

What you'e done here is use Eel to start a web server; when the web page hits a URL, it hits the web server, which is in Python space, which allows you to run Python code. Eel might make it marginally easier to start a web server... but you could do that with any web server (Django, Flask, or even a http.server subclass).

That's fundamentally different to what this ticket is proposing - the approach suggested by this ticket wouldn't require a standalone webserver. It would integrate directly with the webview, so that invoking Javascript calls back into the same process that is hosting the web view.

freakboy3742 avatar Jan 14 '24 01:01 freakboy3742

Oh - in that case, it will need to be implemented in toga 😅

proneon267 avatar Jan 14 '24 02:01 proneon267

An implementation note: If this is resolved for GTK or Winforms, the MapView widget introduced by #2379 will be able to implement the on_select handler.

freakboy3742 avatar Feb 13 '24 03:02 freakboy3742