Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-25862] Parity: Support async Ti.UI.Webview.evalJS call on Android & iOS

GitHub Issuen/a
TypeImprovement
PriorityNone
StatusClosed
ResolutionFixed
Resolution Date2018-09-25T19:47:31.000+0000
Affected Version/sRelease 7.0.2
Fix Version/sRelease 7.5.0
ComponentsAndroid, iOS
Labelsparity
ReporterChristopher Williams
AssigneeChristopher Williams
Created2018-03-13T14:46:56.000+0000
Updated2018-10-17T14:07:30.000+0000

Description

Issue

Evaluating Javascript within a Webview is inherently a (possibly) long-running task. On Windows we evalJS accepts a second argument which is a callback function to receive the result (when it's ready). We should support this async variant of the Ti.UI.WebView.evalJS method on Android and iOS (especially now that there's an async method to do exactly this natively in Android as of apilevel-19).
// Existing sync call example
// Result is returned sync
var result = webview.evalJS('1 + 3');

// Async variant
// this variant doesn't return a a value (basically it returns null or undefined but callers shouldn't use the result)
// It fires off a thread/whatever to eval the JS and call the callback async.
webview.evalJS('1 + 3', function (result) {
  // do something with result
});

Comments

  1. Hans Knöchel 2018-03-13

    The big issue on iOS is that the UIWebView currently used in Ti.UI.WebView does not offer an API to both do the evalJS call async AND receive back a value. It can either be blocked on the main thread (sync) and return a value or do it async but do not return a value. Different to that, the "more modern" WKWebView that slowly replaces Ti.UI.WebView does it different and offers the API asynchronous by default. I've made [a module](https://github.com/appcelerator-modules/Ti.WKWebView) for that one a while ago, so devs can use it already. [~cwilliams] Let me know your thoughts!
  2. Joshua Quick 2018-03-13

    Here's an idea... How about we create an async evalJS() equivalent which does *+not+* support returning a value. It merely executes the given script within the WebView. Developers already have a means to communicate back within the HTML via Ti.App.fireEvent(). They can use that to async communicate with the Titanium JavaScript. Sometimes keeping it simple is better.
  3. Christopher Williams 2018-03-14

    I think because I didn't add the example API we may be getting confused here. An async variant of the method would not return a valid result. It should return null/undefined. Instead it schedules the eval to be run in the Webview and invokes the callback with the result when it's done. Right now the Android version I set up calls the callback function with the result, but doesn't really have a way to handle errors. We could consider a better version which would be more node-like so the first arg is an optional error object, the second argument is the result.
       webview.evalJS('1 + 3', function (err, result) {
         // err may be an Error object or string if there was an issue evaluating the JS.
         // if not, result should be a string
       });
       
    I don't see why iOS wouldn't be able to do this. This is not some magical time-traveling variant that runs async but also returns a value sync.
  4. Joshua Quick 2018-03-14

    Hold on. We can already do async eval on all platforms today. We support Ti.App.addEventListener() and Ti.App.fireEvent() on the HTML side. So, developera already have the ability to do async event driven communication between the WebView's JavaScript and Titanium's JavaScript. And they can do an evalJS() equivalent by passing the JavaScript string via an event to the WebView like the below...
       var htmlText =
       		'<!DOCTYPE html>' +
       		'<html>' +
       		'	<body>' +
       		'		<p>HTML Fire Event Test</p>' +
       		'		<p id="label"></p>' +
       		'	</body>' +
       		'	<script>' +
       		'		Ti.App.addEventListener("app:webViewEval", function(e) {' +
       		'			eval(e.javaScriptString)' +
       		'		});' +
       		'	</script>' +
       		'</html>';
       
       var window = Ti.UI.createWindow();
       var webView = Ti.UI.createWebView({
       	html: htmlText,
       });
       window.add(webView);
       window.open();
       
       var count = 1;
       setInterval(function(e) {
       	Ti.App.fireEvent('app:webViewEval', {
       		javaScriptString: 'document.getElementById("label").innerHTML = "Counter: ' + count + '"',
       	});
       	count++;
       }, 1000);
       
    The above works on both Android and iOS. _*Edit:* The above mentioned solution will only work for HTML under the developer's control. It won't work with random webpages loaded from the Internet since they won't have any Titanium event listeners coded into the HTML's JavaScript. So, Chris' async evalJS() still has merit._
  5. Christopher Williams 2018-09-20

    https://github.com/appcelerator/titanium_mobile/pull/9889 Also needs a thumb-up from either [~gmathews] or [~jquick]
  6. Samir Mohammed 2018-10-17

    *Closing ticket* Verified improvement in SDK Version 7.5.0.v20181016071050 using elements from the example in the description. *Test environment*
       APPC Studio: 5.1.0.201808080937
       iPhone 6 Sim (iOS 12)
       APPC CLI: 7.0.6
       Operating System Name: Mac OS Mojave
       Operating System Version: 10.14
       Node.js Version: 8.9.1
       Xcode 10.0
       

JSON Source