{ "id": "171129", "key": "TIMOB-25786", "fields": { "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false }, "project": { "id": "10153", "key": "TIMOB", "name": "Titanium SDK/CLI", "projectCategory": { "id": "10100", "description": "Titanium and related SDKs used in application development", "name": "Client" } }, "fixVersions": [], "resolution": { "id": "7", "description": "", "name": "Invalid" }, "resolutiondate": "2018-04-17T21:32:36.000+0000", "created": "2018-02-16T18:31:29.000+0000", "priority": { "name": "None", "id": "6" }, "labels": [], "versions": [ { "id": "20060", "description": "", "name": "Release 7.0.2", "archived": false, "released": true, "releaseDate": "2018-02-09" } ], "issuelinks": [ { "id": "56374", "type": { "id": "10001", "name": "Cloners", "inward": "is cloned into", "outward": "is cloned from" }, "inwardIssue": { "id": "171307", "key": "TIMOB-25862", "fields": { "summary": "Parity: Support async Ti.UI.Webview.evalJS call on Android & iOS", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "priority": { "name": "None", "id": "6" }, "issuetype": { "id": "4", "description": "An improvement or enhancement to an existing feature or task.", "name": "Improvement", "subtask": false } } } }, { "id": "56345", "type": { "id": "10002", "name": "Duplicate", "inward": "is duplicated by", "outward": "duplicates" }, "inwardIssue": { "id": "106512", "key": "TIMOB-12095", "fields": { "summary": "Android: EvalJS does not work before a webview is loaded", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "priority": { "name": "High", "id": "2" }, "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false } } } }, { "id": "56364", "type": { "id": "10003", "name": "Relates", "inward": "relates to", "outward": "relates to" }, "outwardIssue": { "id": "171294", "key": "TIMOB-25859", "fields": { "summary": "Android: Delay WebView Ti.App.fireEvent() to be fired after page load", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "priority": { "name": "Medium", "id": "3" }, "issuetype": { "id": "4", "description": "An improvement or enhancement to an existing feature or task.", "name": "Improvement", "subtask": false } } } } ], "assignee": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "updated": "2018-04-17T21:32:36.000+0000", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "components": [ { "id": "10202", "name": "Android", "description": "Android Platform" } ], "description": "h5. Issue\r\nWhen setting run-on-main-thread to true webview.evalJS errors with the following message: \r\n{code}[WARN] TiWebViewBinding: (main) [4577,18530] Timeout waiting to evaluate JS {code}\r\nWhen setting run-on-main-thread to false webview.evalJS calls are executed as expected\r\nThis is important since the customer has to have run-on-main-thread in order to use Hyperloop\r\n\r\nh5. Steps to replicate\r\n1. download project: [^evalJs-bug-demo.zip] \r\n2. Run project to Android simulator and view console log\r\n", "attachment": [ { "id": "64869", "filename": "evalJs-bug-demo.zip", "author": { "name": "vvazquezmontero", "key": "vvazquezmontero", "displayName": "Victor Vazquez Montero", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2018-02-16T18:30:41.000+0000", "size": 723090, "mimeType": "application/zip" } ], "flagged": false, "summary": "Android: WebView eval JS timeout error ", "creator": { "name": "vvazquezmontero", "key": "vvazquezmontero", "displayName": "Victor Vazquez Montero", "active": true, "timeZone": "America/Los_Angeles" }, "subtasks": [], "reporter": { "name": "vvazquezmontero", "key": "vvazquezmontero", "displayName": "Victor Vazquez Montero", "active": true, "timeZone": "America/Los_Angeles" }, "environment": " Operating System\r\n Name = Mac OS X\r\n Version = 10.12.6\r\n Architecture = 64bit\r\n # CPUs = 8\r\n Memory = 16.0GB\r\n\r\nNode.js\r\n Node.js Version = 8.9.3\r\n npm Version = 5.5.1\r\n\r\nAppcelerator CLI\r\n Installer = 4.2.12\r\n Core Package = 7.0.2\r\n\r\nTitanium CLI\r\n CLI Version = 5.0.14\r\n node-appc Version = 0.2.41", "comment": { "comments": [ { "id": "434826", "author": { "name": "vvazquezmontero", "key": "vvazquezmontero", "displayName": "Victor Vazquez Montero", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Hey [~amukherjee] can we have someone check out this ticket.\r\n", "updateAuthor": { "name": "vvazquezmontero", "key": "vvazquezmontero", "displayName": "Victor Vazquez Montero", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2018-02-23T17:13:59.000+0000", "updated": "2018-02-23T17:13:59.000+0000" }, { "id": "434835", "author": { "name": "amukherjee", "key": "amukherjee", "displayName": "Abir Mukherjee", "active": true, "timeZone": "America/Los_Angeles" }, "body": "[~vvazquezmontero], Okay, I'll ask one of our Android devs to look into this.", "updateAuthor": { "name": "amukherjee", "key": "amukherjee", "displayName": "Abir Mukherjee", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2018-02-23T18:56:12.000+0000", "updated": "2018-02-23T18:56:12.000+0000" }, { "id": "434836", "author": { "name": "gmathews", "key": "gmathews", "displayName": "Gary Mathews", "active": true, "timeZone": "America/Los_Angeles" }, "body": "This happens because we prevent the thread being blocked for more than 3500ms to prevent an ANR pop-up.\r\n\r\nhttps://github.com/appcelerator/titanium_mobile/blob/master/android/modules/ui/src/java/ti/modules/titanium/ui/widget/webview/TiWebViewBinding.java#L164\r\n\r\nWe should probably use a callback here instead", "updateAuthor": { "name": "gmathews", "key": "gmathews", "displayName": "Gary Mathews", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2018-02-23T19:11:15.000+0000", "updated": "2018-02-23T19:11:15.000+0000" }, { "id": "434839", "author": { "name": "cwilliams", "key": "cwilliams", "displayName": "Christopher Williams", "active": true, "timeZone": "America/New_York" }, "body": "I think it's a deadlock due to the code being run on the main thread and not having special logic for handling it inline.\r\n\r\nI've never looked at this code before but it appears to make a special JS environment on the web view where it sets up a polling mechanism to grab code snippets off a queue from Java-land, then wraps the code in some wrapping JS code to eval it and set the result back on the java proxy. I imagine we'd have to retool this quite a bit to get it to work in a \"main thread\" way.\r\n\r\nAs Gary suggested, we actually ran into this on Windows where the API is async and requires a second callback argument. I think it'd be a much simpler implementation to move this way on Android as well since they already have an async api to evaluate Javascript without all this polling script injection stuff: https://developer.android.com/reference/android/webkit/WebView.html#evaluateJavascript(java.lang.String,%20android.webkit.ValueCallback)\r\n\r\nI honestly can't immediately think of a nice way to get this to work synchronously on the ui/main thread.\r\n\r\nBut obviously this is a large breaking API change...", "updateAuthor": { "name": "cwilliams", "key": "cwilliams", "displayName": "Christopher Williams", "active": true, "timeZone": "America/New_York" }, "created": "2018-02-23T20:00:56.000+0000", "updated": "2018-02-23T20:00:56.000+0000" }, { "id": "434969", "author": { "name": "cwilliams", "key": "cwilliams", "displayName": "Christopher Williams", "active": true, "timeZone": "America/New_York" }, "body": "Posted a PR with a possible async variant of the API that matches Windows. This would involve the developers/users passing a second argument to evalJS that was an async function that received the result, i.e.: \r\n{code:javascript}\r\nwebview.evalJS('1 + 2', function (result) {\r\n Ti.API.info(result);\r\n});\r\n{code}\r\n\r\nThis PR is meant to get some feedback from Android devs, but may offer an alternative when using main thread. My attempt to spin off a thread to get this to work in a synchronous fashion with run-on-main-thread was unsuccessful.", "updateAuthor": { "name": "cwilliams", "key": "cwilliams", "displayName": "Christopher Williams", "active": true, "timeZone": "America/New_York" }, "created": "2018-02-27T19:33:48.000+0000", "updated": "2018-02-27T19:33:48.000+0000" }, { "id": "435039", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "body": "[~vvazquezmontero],\r\n\r\nFor now, you can work-around this issue by moving the {{evalJS()}} calls out of the custom \"App.DOMContentLoaded\" event and to the {{WebView}} \"load\" event handler like this...\r\n\r\n{code:javascript}\r\n// Don't do this...\r\n/*\r\nTi.App.addEventListener ('App:DOMContentLoaded', function () {\r\n Ti.API.info('DOMContentLoaded listener called in app.js');\r\n wv.evalJS('add_outbrain()');\r\n wv.evalJS('populate_ad_slots()');\r\n});\r\n*/\r\n\r\n// Do this instead. It works on the main thread.\r\nwv.addEventListener('load', function () {\r\n Ti.API.info('WebView load listener called);\r\n wv.evalJS('add_outbrain()');\r\n wv.evalJS('populate_ad_slots()');\r\n}\r\n{code}", "updateAuthor": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2018-02-28T23:43:40.000+0000", "updated": "2018-02-28T23:44:38.000+0000" }, { "id": "435043", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "body": "I've isolated the issue.\r\n\r\nThis is not a thread deadlock issue.\r\n\r\nWhat's happening is that the {{WebView.evalJS()}} function is getting called before the web page has finished loading. We inject a {{POLLING_SCRIPT}} to the page *+after+* it has finished loading via {{TiWebViewClient.onPageFinished()}} here...\r\nhttps://github.com/appcelerator/titanium_mobile/blob/master/android/modules/ui/src/java/ti/modules/titanium/ui/widget/webview/TiWebViewClient.java#L45\r\n\r\nThe polling script's job is to fetch a JavaScript snippet that has been added to the stack by the {{TiWebViewBinding.getJSValue()}} method. This is the method that gets called by {{WebView.evalJS()}} and blocks/waits for the polling script to fetch the snippet, execute it, and return a result. But since the page hasn't finished loading yet and this method blocks the main UI thread, the polling script never gets injected and the snippet in the stack is ignored.\r\nhttps://github.com/appcelerator/titanium_mobile/blob/master/android/modules/ui/src/java/ti/modules/titanium/ui/widget/webview/TiWebViewBinding.java#L151\r\n\r\nNote that this is why my work-around from my previous post works. It's doing an {{evalJS()}} after the page is loaded and after the polling script has been injected.\r\n\r\nNow that we know what's going on (and knowing is half the battle), we can look into fixing it. ;)", "updateAuthor": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2018-03-01T04:07:10.000+0000", "updated": "2018-03-01T04:07:10.000+0000" }, { "id": "435322", "author": { "name": "vvazquezmontero", "key": "vvazquezmontero", "displayName": "Victor Vazquez Montero", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Hey [~jquick] thanks for the update!!", "updateAuthor": { "name": "vvazquezmontero", "key": "vvazquezmontero", "displayName": "Victor Vazquez Montero", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2018-03-07T21:52:42.000+0000", "updated": "2018-03-07T21:52:42.000+0000" }, { "id": "435364", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "body": "[~vvazquezmontero], I'm going to write this up as a separate ticket and flag it as an enhancement. This isn't technically a bug because there are cases where doing a blocking {{evalJS()}} is impossible to execute, such as with [TIMOB-12095] where it's calling {{evalJS()}} before the WebView is being displayed on-screen (I'm sure this would be impossible on iOS as well).\r\n\r\nSo, the enhancements needed are:\r\n* Document that {{evalJS()}} should only be called after the page has been loaded.\r\n* Modify {{fireEvent()}} handling in HTML to be fired after the page has been loaded, not while loading. (This solves the customer case and makes it more convenient to use.)\r\n\r\nI'll write up the ticket later today.", "updateAuthor": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2018-03-07T22:32:12.000+0000", "updated": "2018-03-07T22:32:12.000+0000" }, { "id": "435444", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "body": "I'd like to offer one more work-around.\r\n\r\nOn Android (and I mean only Android), you can async call a JavaScript function in your HTML by doing the following.\r\n{code:javascript}\r\nTi.App.addEventListener ('App:DOMContentLoaded', function () {\r\n wv.url = 'javascript:add_outbrain()';\r\n wv.url = 'javascript:populate_ad_slots()';\r\n});\r\n{code}\r\n\r\nNote the \"javascript:\" URL scheme used above. This is how a native Android developer would do it on Android. Just remember that the above won't work on iOS. This is also something Titanium does not officially document, but it is a feature that Google officially documents for Android here...\r\nhttps://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface(java.lang.Object,%20java.lang.String)\r\n\r\nI'm bringing this up because {{evalJS()}} may still timeout when using a very large HTML page containing a lot of JavaScript on a horribly slow device. Even after you wait for the \"load\" event to be received. For a blocking {{evalJS()}} function call, there is really not much more we can do. As in, there is no way to \"fix\" it. An async call is the only solution in such a case.", "updateAuthor": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2018-03-10T04:57:17.000+0000", "updated": "2018-03-10T04:57:17.000+0000" }, { "id": "435517", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "body": "I've written this up as an enhancement request here: [TIMOB-25859]", "updateAuthor": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2018-03-12T21:33:33.000+0000", "updated": "2018-03-12T21:33:33.000+0000" }, { "id": "435604", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "body": "[~vvazquezmontero],\r\n\r\nHold on. There's a much simpler solution.\r\nWe already support async communications with the WebView on all platforms *+today+*.\r\n\r\nThe HTML's JavaScript has access to APIs {{Ti.App.addEventListener()}} and {{Ti.App.fireEvent()}}. Titanium developers can also make up their own unique names for these events as well. So, instead of doing blocking {{evalJS()}} calls, it would be far far better to do an event-driven approach between the WebView's JavaScript and Titanium's JavaScript.\r\n\r\nFor example, if you want the Titanium side to invoke a JavaScript function on the HTML side, then you can do it like the below, which works on all platforms...\r\n{code:javascript}\r\nvar htmlText =\r\n\t\t'' +\r\n\t\t'' +\r\n\t\t'\t' +\r\n\t\t'\t\t

' +\r\n\t\t'\t' +\r\n\t\t'\t' +\r\n\t\t'';\r\n\r\nvar window = Ti.UI.createWindow();\r\nvar webView = Ti.UI.createWebView({\r\n\thtml: htmlText,\r\n});\r\nwebView.addEventListener(\"load\", function(e) {\r\n\tTi.App.fireEvent(\"app:webViewSetLabel\", { text: \"Hello World\" });\r\n});\r\nwindow.add(webView);\r\nwindow.open();\r\n{code}\r\n\r\nAnd if you want to do an async {{evalJS()}} equivalent, that too can be done via events like the below. Again, the below works on all platforms.\r\n{code:javascript}\r\nvar htmlText =\r\n\t\t'' +\r\n\t\t'' +\r\n\t\t'\t' +\r\n\t\t'\t\t

' +\r\n\t\t'\t' +\r\n\t\t'\t' +\r\n\t\t'';\r\n\r\nvar window = Ti.UI.createWindow();\r\nvar webView = Ti.UI.createWebView({\r\n\thtml: htmlText,\r\n});\r\nwebView.addEventListener(\"load\", function(e) {\r\n\tTi.App.fireEvent('app:webViewEval', {\r\n\t\tjavaScriptString: 'document.getElementById(\"label\").innerHTML = \"Hello World\"',\r\n\t});\r\n});\r\nwindow.add(webView);\r\nwindow.open();\r\n{code}\r\n\r\nNow, the above solution will only work for HTML that's under your control versus some random webpage loaded from the Internet, but from a security standpoint that's probably for the best.", "updateAuthor": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2018-03-14T19:37:11.000+0000", "updated": "2018-03-14T19:37:11.000+0000" } ], "maxResults": 18, "total": 18, "startAt": 0 } } }