beeware / toga

A Python native, OS native GUI toolkit.
https://toga.readthedocs.io/en/latest/
BSD 3-Clause "New" or "Revised" License
4.35k stars 671 forks source link

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

Open appleman4000 opened 11 months ago

appleman4000 commented 11 months ago

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

freakboy3742 commented 11 months ago

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.

proneon267 commented 10 months ago

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.

freakboy3742 commented 10 months ago

@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)?

proneon267 commented 10 months ago

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.

freakboy3742 commented 10 months ago

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?

proneon267 commented 10 months ago

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

proneon267 commented 10 months ago

@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 :)

freakboy3742 commented 10 months ago

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.

proneon267 commented 10 months ago

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

freakboy3742 commented 9 months ago

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.