Skip to content

Commit 5f965cc

Browse files
committed
Function call improvements (only cocoa so far)
1. when calling a Python function multiple times from Javascript, Promises will still get settled r0x0r#390 2. Add a per-browser ID so that return values are routed back to the correct client when you have multiple windows 3. allow calling python functions with multiple arguments
1 parent f0e731b commit 5f965cc

File tree

3 files changed

+46
-43
lines changed

3 files changed

+46
-43
lines changed

webview/js/api.py

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,34 @@
11
src = """
22
window.pywebview = {
33
token: '%s',
4+
_rpc_id_prefix: Math.random().toString(36).substring(2,6)+'.',
5+
_rpc_id_sequence: 0,
46
_createApi: function(funcList) {
57
for (var i = 0; i < funcList.length; i++) {
68
window.pywebview.api[funcList[i]] = (function (funcName) {
7-
return function(params) {
8-
var promise = new Promise(function(resolve, reject) {
9-
window.pywebview._checkValue(funcName, resolve);
9+
return function() {
10+
var rpc_id = window.pywebview._rpc_id_prefix+(
11+
++window.pywebview._rpc_id_sequence);
12+
var rpc_request = {name:funcName, rpc_id:rpc_id, args:Array.from(arguments)}
13+
window.pywebview._bridge.call(rpc_request);
14+
return new Promise(function(resolve, reject) {
15+
function on_rpc_settle(ev){
16+
if (ev.detail.rpc_id == rpc_request.rpc_id){
17+
window.removeEventListener('rpc_settle', on_rpc_settle);
18+
if (ev.detail.success)
19+
resolve(ev.detail.value);
20+
else
21+
reject(ev.detail.value);
22+
}
23+
}
24+
window.addEventListener('rpc_settle', on_rpc_settle);
1025
});
11-
window.pywebview._bridge.call(funcName, JSON.stringify(params));
12-
return promise;
1326
}
1427
})(funcList[i])
15-
16-
window.pywebview._returnValues[funcList[i]] = {
17-
isSet: false,
18-
value: undefined,
19-
}
2028
}
2129
},
2230
_bridge: {
23-
call: function (funcName, params) {
31+
call: function (rpc_request) {
2432
switch(window.pywebview.platform) {
2533
case 'mshtml':
2634
case 'cef':
@@ -29,7 +37,8 @@
2937
case 'edgehtml':
3038
return window.external.notify(JSON.stringify([funcName, params]));
3139
case 'cocoa':
32-
return window.webkit.messageHandlers.jsBridge.postMessage(JSON.stringify([funcName, params]));
40+
return window.webkit.messageHandlers.jsBridge.postMessage(
41+
JSON.stringify(rpc_request));
3342
case 'qtwebengine':
3443
new QWebChannel(qt.webChannelTransport, function(channel) {
3544
channel.objects.external.call(funcName, params);
@@ -38,25 +47,13 @@
3847
}
3948
}
4049
},
41-
42-
_checkValue: function(funcName, resolve) {
43-
var check = setInterval(function () {
44-
var returnObj = window.pywebview._returnValues[funcName];
45-
if (returnObj.isSet) {
46-
returnObj.isSet = false;
47-
try {
48-
resolve(JSON.parse(returnObj.value));
49-
} catch(e) {
50-
resolve(returnObj.value);
51-
}
52-
53-
clearInterval(check);
54-
}
55-
}, 100)
50+
_rpcSettle: function(rpc_id, success, value){
51+
window.dispatchEvent(
52+
new CustomEvent('rpc_settle', {detail:{rpc_id:rpc_id, success:success, value:value}} )
53+
);
5654
},
5755
platform: '%s',
5856
api: {},
59-
_returnValues: {}
6057
}
6158
6259
window.pywebview._createApi(%s);

webview/platforms/cocoa.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,11 @@ def initWithObject_(self, window):
8989
return self
9090

9191
def userContentController_didReceiveScriptMessage_(self, controller, message):
92-
func_name, param = json.loads(message.body())
93-
if param is WebKit.WebUndefined.undefined():
94-
param = None
95-
js_bridge_call(self.window, func_name, param)
92+
rpc_request = json.loads(message.body())
93+
name = rpc_request['name']
94+
rpc_id = rpc_request['rpc_id']
95+
args = rpc_request['args']
96+
js_bridge_call(self.window, name, args, rpc_id)
9697

9798
class BrowserDelegate(AppKit.NSObject):
9899
# Display a JavaScript alert panel containing the specified message

webview/util.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,23 +83,28 @@ def generate_func():
8383
return js_code
8484

8585

86-
def js_bridge_call(window, func_name, param):
86+
def js_bridge_call(window, func_name, args, rpc_id):
8787
def _call():
88-
result = json.dumps(func(func_params)).replace('\\', '\\\\').replace('\'', '\\\'')
89-
code = 'window.pywebview._returnValues["{0}"] = {{ isSet: true, value: \'{1}\'}}'.format(func_name, result)
90-
window.evaluate_js(code)
88+
try:
89+
result = json.dumps(func(*args))
90+
code = 'window.pywebview._rpcSettle({0},true,{1})'.format(json.dumps(rpc_id), result)
91+
window.evaluate_js(code)
92+
except Exception as e:
93+
logger.exception('Error occurred while evaluating function {0}'.format(func_name))
94+
code = 'window.pywebview._rpcSettle({0},false,{1})'.format(json.dumps(rpc_id), str(e))
95+
window.evaluate_js(code)
9196

9297
func = getattr(window.js_api, func_name, None)
9398

9499
if func is not None:
95-
try:
96-
func_params = param if not param else json.loads(param)
97-
t = Thread(target=_call)
98-
t.start()
99-
except Exception:
100-
logger.exception('Error occurred while evaluating function {0}'.format(func_name))
100+
t = Thread(target=_call)
101+
t.start()
101102
else:
102-
logger.error('Function {}() does not exist'.format(func_name))
103+
msg = 'Function {}() does not exist'.format(func_name)
104+
logger.error(msg)
105+
code = 'window.pywebview._rpcSettle({0},false,{1})'.format(
106+
json.dumps(rpc_id), json.dumps(msg))
107+
window.evaluate_js(code)
103108

104109

105110
def escape_string(string):

0 commit comments

Comments
 (0)