tidev / titanium-sdk

🚀 Native iOS and Android Apps with JavaScript
https://titaniumsdk.com/
Other
2.75k stars 1.21k forks source link

Android: Two unreported remote WebView non-freezing IPC techniques #14071

Open Informate opened 2 months ago

Informate commented 2 months ago

I have searched and made sure there are no existing issues for the issue I am filing

URL

https://titaniumsdk.com/guide/Titanium_SDK/Titanium_SDK_How-tos/Integrating_Web_Content/Communication_Between_WebViews_and_Titanium.html

Description

The IPC (Inter Process Communication) technique to be used with remote WebView explained in the guide is limited to the evalJS method. Unfortunately, this method causes freezes in the smoothness of the WebView (only on remote Android WebViews), which are especially visible in the presence of 3D animations made with WebGL (approx 50ms freeze for each evalJS call). To remedy this problem there is another IPC technique available on Android, but it is no longer listed in the guide, perhaps it used to be.

Indeed, on Android it is possible to use an ephemeral cookie in order to exfiltrate data from the WebView without the problem of fluidity freezes caused by evalJS. To do this it is sufficient to place the data to be exfiltrated in a session cookie (thus without specifying an expiration date), additionally to make it an even more ephemeral cookie we can also place the cookie in a page that does not exist in the remote domain, and additionally add (at creation) the said page to the blacklist of pages not viewable by the WebView, via the "blockedURLs" attribute. To set the cookie in case we do not have direct access to the remote code, we can always take advantage of evalJS, for example by inserting an interval timer, which does the work for us from within the remote page.

document.cookie='cookieName=value; path=/non-existing-page; SameSite=Strict';

Optionally we set SameSite to Strict, but we do not enter any expiration to have a session cookie that will be anyway deleted when we close the App (or WebView?).

At this point we can read the Cookie value, that is, the data we wanted to exfiltrate, using:

Ti.Network.getSystemCookies('www.RemoteDomain.com','/not-existing-page','cookieName');

The main shortcoming of this technique is that there is no clear method for signaling when the data is ready; on the other hand, a very smooth execution of the WebView is guaranteed. Be careful that the data is also transferred with some acceptable delay to the app.

If the page that does not exist is not invoked by the WebView, the cookie should remain ephemeral, since it was never sent over the network. (However, the actual ephemerality of the cookie has not been thoroughly tested in practice).

Symmetrically an IPC connection in the other direction could be generated using:

Ti.Network.createCookie({ ... })

(But this functionality has not been tested at the moment.)

More advanced IPC techniques could be implemented opening a Socket on localhost using:

Titanium.Network.Socket.createTCP({ ... })

paired with XMLHttpRequest, Fetch or a WebSocket on the WebView remote page side (ChatGPT could easily code this for you). However the cookies based rudimentary technique fully met my requirements of non freezing WebGL.

Suggestion

Report also this two useful IPC techniques in the guide (especially useful to avoid freezing WebGL animations).

m1ga commented 2 months ago

Unfortunately, this method causes freezes in the smoothness of the WebView

Not sure if I can follow you here. It sounds more like a implementation issue. I was using krpano webviews in older projects without an issue. Why and how often are you running evalJS? Did you try the event system?

In my projects I use the web page inside the webview for all interactions.

Informate commented 2 months ago

It may be an evalJS limitation given by the implementation on Android remote WebView (on iOS and on local Android WebView is ok). I need to read some data approx each 200ms. But already at 2000ms I see the animation freezing for like 50ms giving unsmooth results! While with the ephemeral cookie technique is very smooth but I get the data with some acceptable delay. At the end I moved to a local WebView anyway. (Where the Event system is working!)

PS: Yes, I am working with panorama viewers, but not KRPano. I made an easy Titanium porting of an open source one you can find on GitHub to work in a local WebView.

Informate commented 2 months ago

This is the code by ChatGPT for the first use case:

function createIntervalTimers(cookieValue, remoteDomain) {
var win = Ti.UI.createWindow({
    backgroundColor: 'white'
});

var webView = Ti.UI.createWebView({
    url: 'about:blank'
});

win.add(webView);
win.open();

// Primo timer dentro evalJS
webView.addEventListener('load', function() {
    webView.evalJS(`
        setInterval(function() {
            document.cookie = 'cookieName=${cookieValue}; path=/non-existing-page; SameSite=Strict';
        }, 3000);
    `);
});

// Secondo timer dentro Titanium
setInterval(function() {
    const returnString = JSON.decode(Ti.Network.getSystemCookies(remoteDomain, '/non-existing-page', 'cookieName'));
    Ti.API.info(returnString);
}, 3000);
}

// Esempio di chiamata della funzione
createIntervalTimers("{'attr': 'value', 'oth': 'value'}", 'www.RemoteDomain.com');
Informate commented 2 months ago

function createIntervalTimers(cookieValue, remoteDomain, remotePage) { var win = Ti.UI.createWindow({ backgroundColor: 'white' });

var webView = Ti.UI.createWebView({
    url: remoteDomain + remotePage,
    blockedURLs: [`${remoteDomain}/not-existing-page`]
});

win.add(webView);
win.open();

// Primo timer dentro evalJS per impostare il cookie nella WebView
webView.addEventListener('load', function() {
    webView.evalJS(`
        setInterval(function() {
            document.cookie = 'cookieName=${cookieValue}; path=/non-existing-page; SameSite=Strict';
        }, 3000);
    `);
});

// Secondo timer dentro Titanium per ottenere il cookie dal dominio remoto
setInterval(function() {
    const returnString = JSON.parse(Ti.Network.getSystemCookies(remoteDomain, '/non-existing-page', 'cookieName'));
    Ti.API.info(returnString);
}, 3000);

}

// Funzione per settare il cookie in Titanium function setCookieInTitanium(cookieValue, remoteDomain, remotePage) { Ti.Network.createCookie({ domain: remoteDomain, path: remotePage, name: 'cookieName', value: cookieValue }); }

// Funzione per leggere il cookie nella WebView usando evalJS function readCookieInWebView(webView) { webView.addEventListener('load', function() { webView.evalJS( setInterval(function() { var cookieValue = document.cookie.split('; ').find(row => row.startsWith('cookieName')).split('=')[1]; console.log('Cookie Value: ' + cookieValue); }, 3000); ); }); }

// Esempio di chiamata della funzione createIntervalTimers("{'attr': 'value', 'oth': 'value'}", 'https://www.RemoteDomain.com', '/not-existing-page');

// Esempio di impostazione del cookie in Titanium setCookieInTitanium("{'attr': 'value', 'oth': 'value'}", 'https://www.RemoteDomain.com', '/not-existing-page');

// Esempio di lettura del cookie nella WebView var webView = Ti.UI.createWebView({ url: 'https://www.RemoteDomain.com/not-existing-page' }); readCookieInWebView(webView);

m1ga commented 2 months ago

This is a very specific case and also the code that is generated is not that good. I would prefer a hand made example to show this if any. I'm not sure if we really need something like this in the docs as we already have cookie read example in the remote view section: https://titaniumsdk.com/guide/Titanium_SDK/Titanium_SDK_How-tos/Integrating_Web_Content/Communication_Between_WebViews_and_Titanium.html#remote-web-content and also inside the cookie docs: The following is an example of how to setup and read a cookie on a web view https://titaniumsdk.com/api/titanium/network/cookie.html#overview

Those should be enough for a user to implement something like this in their apps.

The panorama project I did was 2015-2016, can't remember what I did there :smile: But I think it was with https://github.com/tidev/titanium-socketio as we had multiple Samsung Gear VR devices to see some panoramas and a presenter device that was seeing in wich direction each of the devices was looking and could send different panoramas to each Gear VR phone. We had a small network hub where all devices where connected to and constantly firing the position.

Informate commented 2 months ago

This could be where I got the cookie idea from! I do not know if the freezing WebView WEbGL problem was relative to the complexity of my evalJs or to the invocation!

Seeing the beautiful and very large collection of different modules of the highest level, perhaps it would be useful then to include a list in the guide, with a small description of the modules that are considered to be of the highest level, with a link to the related GitHub. E.g. yours, TiDev's and those that are deemed to be of the highest value!

Informate commented 2 months ago

This is a very specific case and also the code that is generated is not that good. I would prefer a hand made example to show this if any. I'm not sure if we really need something like this in the docs as we already have cookie read example in the remote view section: https://titaniumsdk.com/guide/Titanium_SDK/Titanium_SDK_How-tos/Integrating_Web_Content/Communication_Between_WebViews_and_Titanium.html#remote-web-content and also inside the cookie docs: The following is an example of how to setup and read a cookie on a web view https://titaniumsdk.com/api/titanium/network/cookie.html#overview

Those should be enough for a user to implement something like this in their apps.

The panorama project I did was 2015-2016, can't remember what I did there 😄 But I think it was with https://github.com/tidev/titanium-socketio as we had multiple Samsung Gear VR devices to see some panoramas and a presenter device that was seeing in wich direction each of the devices was looking and could send different panoramas to each Gear VR phone. We had a small network hub where all devices where connected to and constantly firing the position.

I have one more question, I am not going to open a new issues as is just logging... but I see errors for the web view on Android:

I am clearing the cookies with Ti.Network.removeAllSystemCookies on startup, and I see:

chromium: [0701/160646.211974:ERROR:variations_seed_loader.cc(37)] Seed missing signature.

Later opening a local web view I see:

[ERROR] chromium: [ERROR:simple_file_enumerator.cc(21)] opendir /data/user/0/[ ... YOUR APP ID ... ]/cache/WebView/Default/HTTP Cache/Code Cache/js: No such file or directory (2) [ERROR] chromium: [ERROR:simple_index_file.cc(615)] Could not reconstruct index from disk

m1ga commented 2 months ago

Don't think it is a Titanium issue as other frameworks have the same output:

do you have any issues with your webview or was it just a question about the log itself? Android is logging many many things that can be ignored (especially maps are very noisy!). But I never look after that log, so I'm not sure what it really is. If it is not creating any issues with your content I would ignore it.

Informate commented 1 month ago

Hi, I have another little doubt: When I close the WebView the Titanium Events system keeps sending me messages for a long time, i.e. is this normal? Should I not worry about it? Specifically the panorama gyro keeps sending signals, but the position stops updating, as if the webview has closed, I have also already called Release on the WebView, but it continues for quite a while. Could it be because of an event queue that is being handled slowly?

m1ga commented 1 month ago

That I can't tell you but my guess would be that sending gyro constantly form the webview to the app will queue up but I think it is super easy to fix when you just remove the event listener or add a boolean variable that won't compute the gyro events when its closed/false. Or even reduce the events that you are sending (smoothing out the gyro) in the webview might also help. But only you can test that and see if it is producing any errors, I'm just guessing here :smile: