Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions android/modules/ui/src/java/ti/modules/titanium/ui/WebViewProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import android.print.PrintManager;
import android.util.DisplayMetrics;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback;
import android.webkit.WebView;

Expand All @@ -34,12 +35,17 @@
import org.appcelerator.titanium.io.TiFileFactory;
import org.appcelerator.titanium.util.TiConvert;
import org.appcelerator.titanium.view.TiUIView;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import ti.modules.titanium.ui.widget.webview.TiUIWebView;

Expand Down Expand Up @@ -76,6 +82,7 @@ public class WebViewProxy extends ViewProxy implements Handler.Callback, OnLifec
private static String fpassword;
PrintManager printManager;
private Message postCreateMessage;
private JSInterface jsInterface;

@Kroll.constant
public static final int PDF_PAGE_DIN_A4 = 0;
Expand Down Expand Up @@ -127,6 +134,11 @@ public TiUIView createView(Activity activity)
postCreateMessage = null;
}

// Add script message handlers if attempted before the web-view could be created.
if (jsInterface != null) {
jsInterface.addPendingScriptMessageHandlers(webView);
}

return webView;
}

Expand Down Expand Up @@ -157,6 +169,24 @@ public Object evalJS(String code, @Kroll.argument(optional = true) KrollFunction
return view.getJSValue(code);
}

@Kroll.method
public void addScriptMessageHandler(String name)
{
if (jsInterface == null) {
jsInterface = new JSInterface(this);
}

jsInterface.addScriptMessageHandler(name);
}

@Kroll.method
public void removeScriptMessageHandler(String name)
{
if (jsInterface != null) {
jsInterface.removeScriptMessageHandler(name);
}
}

@Kroll.getProperty
public String getHtml()
{
Expand Down Expand Up @@ -634,6 +664,11 @@ public void onStop(Activity activity)
@Override
public void onDestroy(Activity activity)
{
if (jsInterface != null) {
jsInterface.destroy();
jsInterface = null;
}

TiUIWebView webView = (TiUIWebView) peekView();
if (webView == null) {
return;
Expand Down Expand Up @@ -693,4 +728,106 @@ public void onReceiveValue(String value)
});
}
}

private class JSInterface
{
// Store references to add later the web-view is created.
private Set<String> pendingScriptMessageHandlers = new HashSet<>();
private Set<String> scriptMessageHandlers = new HashSet<>();
private final HashMap<String, Integer> appListeners = new HashMap<>();
private WeakReference<WebViewProxy> proxy;

public JSInterface(WebViewProxy proxy)
{
this.proxy = new WeakReference<>(proxy);
}

// To be called only when the web-view is created and before setting any URL.
public void addPendingScriptMessageHandlers(TiUIWebView tiUIWebView)
{
WebView webView = tiUIWebView.getWebView();
for (String name : pendingScriptMessageHandlers) {
scriptMessageHandlers.add(name);
webView.addJavascriptInterface(this, name);
}

pendingScriptMessageHandlers.clear();
}

public void addScriptMessageHandler(String name)
{
if (view == null) {
pendingScriptMessageHandlers.add(name);
return;
}

scriptMessageHandlers.add(name);
((TiUIWebView) view).getWebView().addJavascriptInterface(this, name);
}

public void removeScriptMessageHandler(String name)
{
if (view == null) {
return;
}

scriptMessageHandlers.remove(name);
((TiUIWebView) view).getWebView().removeJavascriptInterface(name);
}

@JavascriptInterface
public void postMessage(String json)
{
if (this.proxy == null || this.proxy.get() == null) {
return;
}

KrollDict dict = null;
try {
if (json != null && !json.equals("undefined")) {
dict = new KrollDict(new JSONObject(json));
}
} catch (JSONException e) {
Log.e(TAG, "Error parsing scriptMessageHandler's JSON message", e);
}

if (dict == null) {
return;
}

// This is just to keep parity with the iOS structure, no other use.
String name = dict.getString("name");
if (name == null) {
Log.e(TAG, "scriptMessageHandler 'name' missing");
return;
}

if (!scriptMessageHandlers.contains(name)) {
Log.e(TAG, "scriptMessageHandler missing for name: " + name);
return;
}

this.proxy.get().fireEvent("message", dict);
}

// Clear local proxy, all script message handlers and their interfaces.
public void destroy()
{
pendingScriptMessageHandlers.clear();

if (this.proxy != null) {
this.proxy.clear();
this.proxy = null;
}

if (view != null) {
WebView webView = ((TiUIWebView) view).getWebView();
for (String name : scriptMessageHandlers) {
webView.removeJavascriptInterface(name);
}
}

scriptMessageHandlers.clear();
}
}
}
42 changes: 26 additions & 16 deletions apidoc/Titanium/UI/WebView.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ description: |

Scripts downloaded from remote web servers cannot access the Titanium namespace.

To interact with remote content, wait until the content is loaded, then use the
To read data from the remote web page, wait until the content is loaded, then use the
[evalJS](Titanium.UI.WebView.evalJS) method to execute a JavaScript expression
inside the web view and retrieve the value of an expression.

To trigger a message from the remote web page, refer <Titanium.UI.WebView.addScriptMessageHandler> usages.

You can inject the local `Ti.App.fireEvent` bindings yourself by adding a script element using
evalJS.

Expand All @@ -51,8 +53,6 @@ description: |
```
The `binding.min.js` is available in the [repository](https://github.com/tidev/titanium-sdk/tree/master/android/modules/ui/assets/Resources/ti.internal/webview).

For iOS check the example in <Titanium.UI.WebView.addScriptMessageHandler>.

#### Local JavaScript Files

During the build process for creating a package, all JavaScript files, that is, any file with a
Expand Down Expand Up @@ -419,28 +419,38 @@ methods:
- name: addScriptMessageHandler
summary: Adds a script message handler.
description: |
Adding a script message handler with name 'name' causes the JavaScript function
window.webkit.messageHandlers.name.postMessage(messageBody) to be defined in all
frames in the web view. You use the same 'name' in your webview's html.
Example JS code for the webview page:
Adding a script message handler with name 'name' causes the JavaScript function postMessage(param)
to be defined in all frames in the web view. You use the same 'name' in your webview's html.
Important: Call this method before setting the URL, or reload the URL if added later.
JS code usages on your web page script:
```
<script type="text/javascript">
// For iOS, the script-message handler is added to the `window.webkit.messageHandlers` object.
window.webkit.messageHandlers.yourHandlerName.postMessage({
message: 'This is an example string.'
});

// For Android, it's added directly to the `window` object, and only accepts the string type argument.
window.yourHandlerName.postMessage(JSON.stringify({
name: 'yourHandlerName', // Must to be passed so the app can differentiate multiple script message handlers.
body: {
message: 'This is an example string.'
},
}));
</script>
```
Connection to the webview:
Titanium app code usages:
```js
webView.addScriptMessageHandler('yourHandlerName');
webView.addEventListener('message', ({ name }) => {
if (name === 'yourHandlerName') {
webView.addEventListener('message', (e) => {
if (e.name === 'yourHandlerName') {
console.log(e.body.message);
}
});
webView.url = 'some-remote-url';
```
platforms: [iphone, ipad, macos]
since: {iphone: "8.0.0", ipad: "8.0.0", macos: "9.2.0"}
platforms: [android, iphone, ipad, macos]
since: {android: "13.1.0", iphone: "8.0.0", ipad: "8.0.0", macos: "9.2.0"}
parameters:
- name: handlerName
summary: |
Expand All @@ -450,8 +460,8 @@ methods:

- name: removeScriptMessageHandler
summary: Removes a script message handler.
platforms: [iphone, ipad, macos]
since: {iphone: "8.0.0", ipad: "8.0.0", macos: "9.2.0"}
platforms: [android, iphone, ipad, macos]
since: {android: "13.1.0", iphone: "8.0.0", ipad: "8.0.0", macos: "9.2.0"}
parameters:
- name: name
summary: The name of the message handler.
Expand Down Expand Up @@ -652,8 +662,8 @@ events:
description: |
This event get fired when you have added a message handler using <Titanium.UI.WebView.addScriptMessageHandler>
and the webpage sends a message to it.
platforms: [iphone, ipad, macos]
since: {iphone: "8.0.0", ipad: "8.0.0", macos: "9.2.0"}
platforms: [android, iphone, ipad, macos]
since: {android: "13.1.0", iphone: "8.0.0", ipad: "8.0.0", macos: "9.2.0"}
properties:
- name: url
summary: URL of the web document being loaded.
Expand Down
Loading