Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A call from CEF to js_api to a Python standard json library's dumps() returns a promise, but the resolved promise returns an object and not a string #352

Closed
darthglowball opened this issue Jul 18, 2019 · 15 comments

Comments

@darthglowball
Copy link

darthglowball commented Jul 18, 2019

Specification

  • pywebview version: I don't know where to find the version. There's no readme in the webview folder. Folder was created on 5th of june 2019.

  • platform / version: Windows 7, 32-bit

Description

A call from CEF to js_api to a Python standard json library's dump() returns a promise, but the resolved promise returns an object and not a string

Here is the Javascript:

   function json_stringify(object, loaded_handler){
       promise1 = window.pywebview.api.json_stringify(object)
       promise1.then((value) => {
           console.log("type " + typeof(value))
           loaded_handler(value)
       })
   }
   
   setTimeout(function(){
       json_stringify({}, console.log)
   }, 3000)

The Python:

class python_interpreter_api:
    def json_stringify(self, obj):
        return str(json.dumps(obj))
        
python_interpreter_api = python_interpreter_api()

Side question: is there a way to make the js_api run Python functions synchronously? So that the javascript is halted, python is run, then javascript continues?

Note: I even added str() to json.dumps (which should be redundant) but that doesn't work either.

@darthglowball darthglowball changed the title A call from CEFPython to js_api to a Python standard json library's dump() returns a promise, but the resolved promise returns an object and not a string A call from CEF to js_api to a Python standard json library's dump() returns a promise, but the resolved promise returns an object and not a string Jul 18, 2019
@darthglowball darthglowball changed the title A call from CEF to js_api to a Python standard json library's dump() returns a promise, but the resolved promise returns an object and not a string A call from CEF to js_api to a Python standard json library's dumps() returns a promise, but the resolved promise returns an object and not a string Jul 18, 2019
@r0x0r
Copy link
Owner

r0x0r commented Jul 22, 2019

You don't have to use json.dumps, but you can return a dict as is. It will be automatically converted to a Javascript object

@darthglowball
Copy link
Author

@r0x0r I don't understand you. In Python, json.dumps returns a string, which I want to use in CEF. I know there's the javascript JSON.stringify, which does the same, but I was testing wether it's possible in principle, to get to know the js_api. My function json_stringify in Python returns a string, and in my Javascript's json_stringify when the promise is solved it returns an object, but what to do with it? For instance, when using {"tab" : {"def" : ["yeah"]}} as argument to my Javascript json_stringify, and when inspected in CEF console, the object doesn't contain my string. I don't even know what type of object this is, as typeof() says "object".

@r0x0r
Copy link
Owner

r0x0r commented Jul 22, 2019 via email

@darthglowball
Copy link
Author

darthglowball commented Jul 22, 2019

JSON.stringify/json.dumps returns a string representation of a javascript/python object, but JSON is not a data type in any of the languages, so I don't think automatic type conversion is happening. JSON can be Object -> String conversion and the other way round, in any language that supports objects and strings. What you're talking about is 1:1 type conversion, where a data structure from one language has an analog in the other (i.e. Javascript Object -> Python Dict).

I am still figuring out js_api's purpose, so bear with me. As I understand it, you can call a python function from javascript, asynchronously (because of the promise returned). Well, in that case I expect json_stringify to do as I tell it: when the promise resolves, I expect my JSON string, just like Python's json.dumps and just like Javascript's JSON.stringify does. But it doesn't: the resolved promise gives me this object of type "object". Even if I were happy with that "object", I look further and there's no JSON formatted string to be found inside.

@r0x0r
Copy link
Owner

r0x0r commented Jul 22, 2019

Sorry I am not following this conversation at all.

Here is how a js api call is made.

def js_bridge_call(window, func_name, param):

Another file worth investigating is https://github.com/r0x0r/pywebview/blob/master/webview/js/api.py

@darthglowball
Copy link
Author

darthglowball commented Jul 22, 2019

Thanks for the link. Please try this easier example (I'm using CEF):

Case 1
JS:

       function get_string(loaded_handler){
           promise1 = window.pywebview.api.get_string()
           promise1.then((value1) => {
               console.log(typeof(value1))
               loaded_handler(value1)
           })
       }
       
       setTimeout(function(){
           get_string(console.log)
       }, 1000)

Python:

my_string = json.dumps({"tab" : {"def" : ["yeah"]}})
print(type(my_string))

class python_interpreter_api:
    def get_string(self, item):
        return my_string
        
python_interpreter_api = python_interpreter_api()

Javascript Output:

object
Object

Python Output:
<class 'str'>

Case 2
Now set my_string equal to '"tab" : {"def" : ["yeah"]}}' and watch the outputs:

Javascript Output:

string
"tab" : {"def" : ["yeah"]}}

Python Output:
<class 'str'>

json.dumps returns a string in Python, so it doesn't make sense that javascript receives some random object and not a string when my_string uses json.dumps. I can't find my_string inside this random object. The only difference is that a literal notation is used in the second case. But we're talking about strings in both cases.

EDIT: I know I forgot a brace in case 2, but this shouldn't lead to the differing outputs that you see, where case 1 shows "object" as the resolved promise and case 2 shows "string" as the resolved promise. In any case strings are sent to js_api, but somewhere there is some implicit conversion happening to a JS object when my_string is JSON formatted.

@r0x0r
Copy link
Owner

r0x0r commented Jul 24, 2019

This works as expecteda as JSON serialization is used for converting types
{a: 4} produces a dict
'{a: 4}' produces a string

@darthglowball
Copy link
Author

js_api should not implicitly convert my_string to a javascript object type! I figured out in what circumstances this implicit Python string to JS object conversion takes place:

  1. my_string is of type Python str.
  2. my_string contains a correctly JSON formatted text. (for instance: my_string = '{"a": {"b": ["c"]}}'

Javascript output says the resolved promise is of type object.

If you set my_string to anything other than JSON, such as my_string = 'hello world', the javascript output says the resolved promise is of type string.

Why do you think this implicit conversion from a Python string type to a JS object type is logical if the string happens to be JSON formatted? Why not let the user decide in their JS code what to do with Python dictionary syntax or JSON syntax inside a JS string that came from a Python string ( my_string = '{"a": {"b": ["c"]}}' ) ?

@r0x0r
Copy link
Owner

r0x0r commented Jul 26, 2019

Took me while to understand the issue, but I am getting there. Please bear with me 😆
I think this is a case of missing quotes around value on line 90 in webview/util.js. In other words, JSON string loses its quotes and its value gets evaluated instead.

@darthglowball
Copy link
Author

Hahaha @r0x0r you had me on the edge of walking away mad; me screaming: "let the bugs be set free!!11" or me posting a mathematical logical proof of the problem with svg flow chart diagrams in the hopes that it would become clear. ^-^

@r0x0r
Copy link
Owner

r0x0r commented Jul 27, 2019

I have pushed a fix to master. Could you verify if it works for you?

@darthglowball
Copy link
Author

Firstly, I get relative import errors with your import in test_js_api.py (using python 3.7) when it is in the webview folder. I used from webview.util import run_test, assert_js to fix that. Then I replaced the util.py with the one on the master, but when running test_js_api.py I get this error:

Traceback (most recent call last): File "C:\Users\hp\Documents\Programming\Web development\File content searcher and categorizer\Client\old\test_js_api.py", line 1, in <module> import webview File "C:\Users\hp\AppData\Local\Programs\Python\Python37-32\lib\site-packages\webview\__init__.py", line 25, in <module> from webview.util import base_uri, parse_file_type, escape_string, transform_url, make_unicode, escape_line_breaks, inject_base_uri File "C:\Users\hp\AppData\Local\Programs\Python\Python37-32\lib\site-packages\webview\util.py", line 19, in <module> from .js import api, npo, dom ImportError: cannot import name 'dom' from 'webview.js' (C:\Users\hp\AppData\Local\Programs\Python\Python37-32\lib\site-packages\webview\js\__init__.py)

@r0x0r
Copy link
Owner

r0x0r commented Jul 28, 2019

Pull the master branch, then execute in the pywebview root directory

PYTHONPATH=. pytest tests/test_js_api.py -s

@darthglowball
Copy link
Author

Did you mean "download" the master branch? Pulling means submitting my own project branch for review to be potentially merged with a master, right? Anyway, I downloaded the master. I never use cmd environment variables, so could you explain how to run that? Why do I need PYTHONPATH and can't I run test_js_api.py from its folder? I don't have PYTHONPATH available so I do:

set PYTHONPATH=%PYTHONPATH%;. pytest tests

I put pywebview folder in site-packages. And in the pywebview folder I run:

test_js_api.py -s

But I get something with not recognized as internal command error.

@r0x0r
Copy link
Owner

r0x0r commented Aug 17, 2019

Fix is deployed to 3.0.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants