Skip to content

Conversation

@prashantsaini1
Copy link
Contributor

@prashantsaini1 prashantsaini1 commented Oct 27, 2025

This PR implements the addScriptMessageHandler and the removeScriptMessageHandler methods for Android platform, bringing parity with the iOS.

Titanium App:

const HANDLER_NAME = 'MyScriptMessageHandler';
const window = Ti.UI.createWindow();
const webView = Ti.UI.createWebView({ url: 'some-remote-url' });

webView.addScriptMessageHandler(HANDLER_NAME, true);
webView.addEventListener('message', (e) => {
	if (e.name === HANDLER_NAME) {
		alert(e.body.message);
	}
});

window.add(webView);
window.open();

Web page:

<script type="text/javascript">
    // For iOS, to keep backward compatibility, do not pass second argument, or set it to `false`.
    const body = { message: 'Hello Titanium!'};
    window.webkit.messageHandlers.yourHandlerName.postMessage(body);
    
    // For Android, or for iOS with second argument as `true`.
    const body = { message: 'Hello Titanium!'};
    window.tisdk.emit('yourHandlerName', JSON.stringify(body));
</script>

@m1ga
Copy link
Contributor

m1ga commented Oct 27, 2025

The example code will show No app side handler available. when I run it on a Pixel 9

@prashantsaini1
Copy link
Contributor Author

prashantsaini1 commented Oct 27, 2025

@m1ga It might have something to do with the synchronisation of the web-page script loading and adding the app side message handler. Can you try to call the web-page side method postMessage upon a button click or anything which does not run the script instantly?

I tested both Android and iOS changes upon a button click rather than instantly running the script, but I added the sample code here for simplicity.

@m1ga
Copy link
Contributor

m1ga commented Oct 27, 2025

I've used the Chrome Devtools to inspect the webview and even using the console I don't have window.MyScriptMessageHandler

@prashantsaini1
Copy link
Contributor Author

@m1ga I checked the native docs, and found that the script must be injected either before the URL is loaded or the URL must be reload after. In my tests, the web-page does some redirections. Can you please set the webView URL after the window.open() call in the Ti App side and let me know? Thanks!

@m1ga
Copy link
Contributor

m1ga commented Oct 27, 2025

Still no luck:

const HANDLER_NAME = 'MyScriptMessageHandler';

const window = Ti.UI.createWindow({ title: 'Android Script Message' });
const webView = Ti.UI.createWebView({ });

window.add(webView);

webView.addScriptMessageHandler(HANDLER_NAME);
webView.addEventListener('message', (e) => {
	if (e.name === HANDLER_NAME) {
		alert(e.body?.message ?? 'No message');
	}
});

window.addEventListener("open", function(){
	webView.url = '/page.html'
})

window.open();

and

<h1>test</h1>
<script>
  const androidHandler = window.MyScriptMessageHandler?.postMessage;
  const iOSHandler = window.webkit?.messageHandlers?.MyScriptMessageHandler?.postMessage;


  document.querySelector("h1").addEventListener("click", function() {
    if (androidHandler) {
      androidHandler(
        JSON.stringify({
          name: 'MyScriptMessageHandler',
          body: {
            message: 'Titanium WebView Rocks!'
          },
        }),
      )
    } else if (iOSHandler) {
      iOSHandler({
        message: 'Titanium WebView Rocks!'
      });
    } else {
      // No app side handlers available.
      alert('No app side handler available.')
    }
  })
</script>

and I click on the h1 and get the "no app side..." alert. I've fetch the PR again and rebuild the SDK just to make sure it's running the correct version. Tested liveview and no liveview. But I don't see the correct alert

@m1ga
Copy link
Contributor

m1ga commented Oct 27, 2025

This example code works now:

const HANDLER_NAME = 'MyScriptMessageHandler';

const window = Ti.UI.createWindow({
	title: 'Android Script Message'
});
const webView = Ti.UI.createWebView({});
window.add(webView);

window.addEventListener("open", function() {
	webView.addScriptMessageHandler(HANDLER_NAME);
	webView.addEventListener('message', (e) => {
		if (e.name === HANDLER_NAME) {
			alert(e.body?.message ?? 'No message');
		}
	});
	webView.url = '/page.html'
})

window.open();

addScriptMessageHandler has to be called after open otherwise view is null in the check and it won't add the listeners.

HTML part:

<!DOCTYPE html>
<html lang="en" dir="ltr">

<head>
  <meta charset="utf-8">
  <title></title>
</head>

<body>
  <h1>test</h1>
  <script>
    document.querySelector("h1").addEventListener("click", function() {

      const androidHandler = window.MyScriptMessageHandler;
      const iOSHandler = window.webkit?.messageHandlers?.MyScriptMessageHandler;

      document.querySelector("h1").addEventListener("click", function() {
        if (androidHandler) {
          androidHandler.postMessage(
            JSON.stringify({
              name: 'MyScriptMessageHandler',
              body: {
                message: 'Titanium WebView Rocks!'
              },
            }),
          )
        } else if (iOSHandler) {
          iOSHandler.postMessage({
            message: 'Titanium WebView Rocks!'
          });
        } else {
          // No app side handlers available.
          alert('No app side handler available.')
        }
      })
    })
  </script>

</body>

</html>

(works locally too)

@prashantsaini1
Copy link
Contributor Author

prashantsaini1 commented Oct 27, 2025

@m1ga Yep! First issue was referencing the postMessage in Web Page code directly, brings in a different closure. Fixing the addScriptMessageHandler order sequence issue as I noticed it too. Just updated the example as well.

Edit: The problem is calling the addScriptMessageHandler without the window being properly opened or when the proxy's view is null.

I've updated the code in description for now, and will check further if we can handle it inside the SDK itself.

@prashantsaini1 prashantsaini1 marked this pull request as draft October 27, 2025 15:44
@prashantsaini1
Copy link
Contributor Author

@m1ga Just fixed the code (refer description), tested with http://migaweb.de/page.html?123 and our local web page as well.

@prashantsaini1 prashantsaini1 marked this pull request as ready for review October 28, 2025 10:01
@prashantsaini1
Copy link
Contributor Author

prashantsaini1 commented Oct 28, 2025

@janvennemann PR's ready now to proceed further as discussed.

@m1ga
Copy link
Contributor

m1ga commented Oct 28, 2025

@prashantsaini1 if you click on the text multiple time it will fire the event multiple times

@prashantsaini1
Copy link
Contributor Author

@m1ga It's because your web page code which you posted is adding the event listener again inside the listener itself. ;)

@m1ga
Copy link
Contributor

m1ga commented Oct 28, 2025

oh 😄 didn't notice the double click event! Then it's working fine now 👍

@janvennemann
Copy link
Contributor

First off, thanks a lot @prashantsaini1 for investigating this matter and adding the basic feature parity. This is a great basic start. I'd love to add a unified API for this that does the heavy lifting behind the scenes and does not require so much platform specific extra logic. Right now it is very cumbersome to set up all the required handlers.

I'm thinking of a simple event based API that can be used like this inside the web view:

window.tiWebViewBridge.emit(eventName: string, data: any)

And then on the Titanium side handle it like any other event:

webView.addEventListener('my-custom-event', ({ name, data }) => {
  console.log({ name, data })
})

For this we would use automatically injected JS code that prepares a single script message handler which is used to send the event that we unwrap and forward on the native side into the Titanium event bus. I totally understand this may be out of scope for this PR but i just wanted to leave my two cents how we could improve this with a proper cross-platform API.

@m1ga
Copy link
Contributor

m1ga commented Oct 28, 2025

for that you can look at #13835 which will inject the current binding.js file in an external page.

@janvennemann
Copy link
Contributor

janvennemann commented Oct 28, 2025

for that you can look at #13835 which will inject the current binding.js file in an external page.

Ah nice, but that is also Android only and is again a totally different API than what iOS uses. That's why i mentioned a unified API that works the same on both platforms.

Just to be sure i'm not missing something what's the benefit of this PR compared #13835. Doesn't it practically do the same, e.g. sending events from the web view to the Titanium app.

@m1ga
Copy link
Contributor

m1ga commented Oct 28, 2025

@janvennemann I just wanted to show you how you could inject the bindings. My old PR will just use the normal Ti namespace (https://titaniumsdk.com/guide/Titanium_SDK/Titanium_SDK_How-tos/Integrating_Web_Content/Communication_Between_WebViews_and_Titanium.html) that has been there for ages and is added in local pages automatically. My PR will just add it for external pages (and yes, only for Android as I don't know how to do it for iOS :) )

You could use that and add a different namespace too but the Ti should stay as many people use that.

@prashantsaini1
Copy link
Contributor Author

prashantsaini1 commented Oct 28, 2025

@m1ga @janvennemann Well, I found some interesting stuff on iOS.

If we enable the _Ti_ message handler on the app side, we can actually invoke the Ti.App.fireEvent(...) on the web-page, just have to wait for the script to be initially loaded.

I tested it on our web-page and already works fine without the window.webkit/window.Android stuff.

App side:

Ti.App.addEventListener('myCustomEvent', (e) => {
	Ti.API.error('Event from webview = ' + JSON.stringify(e));
});

// iOS already injects the `Ti.App` namespace script under the `_Ti_` handler.
webView.addScriptMessageHandler('_Ti_');

Web page side:

 Ti.App.fireEvent('myCustomEvent', 'pass any data here, even a json object as usual');

Next Steps:

  • iOS: Auto invoke the webView.addScriptMessageHandler('_Ti_'); in iOS SDK when the relevant script is injected.
  • Android: review the PR from @m1ga if that works already, or check how it can be done on Android.

@janvennemann
Copy link
Contributor

Ha, thanks @prashantsaini1, you beat me to it. I found the same after digging through our web view implementation thanks to @m1ga's hints.

The iOS implementation is also limited to local URLs, by manually invoking webView.addScriptMessageHandler('_Ti_'); you basically manually doing this step

I think we should just introduce a new property like autoInjectEventBridge which controls this behavior. On iOS it set's the script message handler, on Android it loads the bindings file. It can be false by default to match existing behavior and possibly for security concerns.

@prashantsaini1
Copy link
Contributor Author

@janvennemann Sounds good. @m1ga I'd take your PR further to enable it for Android as well. :)

@m1ga
Copy link
Contributor

m1ga commented Oct 28, 2025

sounds good 👍 Not sure if that might be relevant for your page too (in case links that open with target:_blank): #14005 but it can be useful when you work with external pages and you don't want to remove target:_blank by hand all the time

@prashantsaini1
Copy link
Contributor Author

prashantsaini1 commented Oct 31, 2025

@m1ga @janvennemann Finally achieved the following syntax for both platforms. To keep backward compatibility without breaking existing iOS apps, added 2nd argument to enable the unified API.

const body = { message: 'Hello Titanium!'};
window.tisdk.emit('myCustomHandler', JSON.stringify(body));

Screenshot 2025-10-31 at 12 22 57 PM

@prashantsaini1 prashantsaini1 requested a review from m1ga November 1, 2025 14:39
Copy link
Contributor

@m1ga m1ga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added some notes. The changes in the Web code will make it work on both platforms if you copy and paste it. Otherwise it will complain on Android because webkit is not there and const body is already there.

Copy link
Contributor

@m1ga m1ga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@prashantsaini1 prashantsaini1 force-pushed the android/webview-scriptmessagehandler branch from ba191e0 to a4ecd42 Compare November 2, 2025 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants