Skip to content

Commit

Permalink
Merge branch 'master' into tiappxml
Browse files Browse the repository at this point in the history
  • Loading branch information
garymathews committed Sep 26, 2018
2 parents 874cf7e + 2909c0e commit 86f5857
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
/**
* Appcelerator Titanium Mobile
* Copyright (c) 2009-2016 by Appcelerator, Inc. All Rights Reserved.
* Copyright (c) 2009-2018 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
package ti.modules.titanium.ui;

import android.app.Activity;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.webkit.ValueCallback;
import android.webkit.WebView;

import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

import org.appcelerator.kroll.KrollDict;
import org.appcelerator.kroll.KrollFunction;
import org.appcelerator.kroll.KrollObject;
import org.appcelerator.kroll.KrollRuntime;
import org.appcelerator.kroll.annotations.Kroll;
import org.appcelerator.kroll.common.AsyncResult;
import org.appcelerator.kroll.common.Log;
Expand All @@ -22,10 +36,6 @@
import org.appcelerator.titanium.view.TiUIView;

import ti.modules.titanium.ui.widget.webview.TiUIWebView;
import android.app.Activity;
import android.os.Handler;
import android.os.Message;
import android.webkit.WebView;
// clang-format off
@Kroll.proxy(creatableInModule = UIModule.class,
propertyAccessors = {
Expand Down Expand Up @@ -60,10 +70,13 @@ public class WebViewProxy extends ViewProxy implements Handler.Callback, OnLifec
private static final int MSG_SET_HEADERS = MSG_FIRST_ID + 113;
private static final int MSG_GET_HEADERS = MSG_FIRST_ID + 114;
private static final int MSG_ZOOM_BY = MSG_FIRST_ID + 115;
private static final int MSG_EVAL_JS = MSG_FIRST_ID + 116;

protected static final int MSG_LAST_ID = MSG_FIRST_ID + 999;
private static String fusername;
private static String fpassword;
private static int frequestID = 0;
private static Map<Integer, EvalJSRunnable> fevalJSRequests = new HashMap<Integer, EvalJSRunnable>();

private Message postCreateMessage;

Expand Down Expand Up @@ -100,7 +113,7 @@ public TiUIWebView getWebView()
}

@Kroll.method
public Object evalJS(String code)
public Object evalJS(String code, @Kroll.argument(optional = true) KrollFunction callback)
{
// If the view doesn't even exist yet,
// or if it once did exist but doesn't anymore
Expand All @@ -112,9 +125,66 @@ public Object evalJS(String code)
Log.w(TAG, "WebView not available, returning null for evalJS result.");
return null;
}
if (callback != null) {
EvalJSRunnable runnable = new EvalJSRunnable(view, getKrollObject(), code, callback);
// When on Android 19+ we can use the builtin evalAsync method!
if (Build.VERSION.SDK_INT >= 19) {
if (TiApplication.isUIThread()) {
runnable.runAsync();
} else {
// Stick the runnable in a static map so we can pull it out in handleMessage()
final int requestID = frequestID++;
fevalJSRequests.put(requestID, runnable);
Message message = getMainHandler().obtainMessage(MSG_EVAL_JS);
message.arg1 = requestID;
message.sendToTarget();
}
} else {
// Just do our sync eval in a separate Thread. Doesn't need to be done in UI
Thread clientThread = new Thread(runnable, "TiWebViewProxy-" + System.currentTimeMillis());
clientThread.setPriority(Thread.MIN_PRIORITY);
clientThread.start();
}
return null;
}
// TODO deprecate the sync variant?
return view.getJSValue(code);
}

private class EvalJSRunnable implements Runnable
{
private final TiUIWebView view;
private final KrollObject krollObject;
private final String code;
private final KrollFunction callback;

public EvalJSRunnable(TiUIWebView view, KrollObject krollObject, String code, KrollFunction callback)
{
this.view = view;
this.krollObject = krollObject;
this.code = code;
this.callback = callback;
}

public void run()
{
// Runs the "old" API we built
String result = view.getJSValue(code);
callback.callAsync(krollObject, new Object[] { result });
}

public void runAsync()
{
// Runs the newer API provided by Android
view.getWebView().evaluateJavascript(code, new ValueCallback<String>() {
public void onReceiveValue(String value)
{
callback.callAsync(krollObject, new Object[] { value });
}
});
}
}

// clang-format off
@Kroll.method
@Kroll.getProperty
Expand Down Expand Up @@ -211,6 +281,13 @@ public boolean handleMessage(Message msg)
case MSG_ZOOM_BY:
getWebView().zoomBy(TiConvert.toFloat(getProperty(TiC.PROPERTY_ZOOM_LEVEL)));
return true;
case MSG_EVAL_JS:
final int evalJSRequestID = msg.arg1;
if (fevalJSRequests.containsKey(evalJSRequestID)) {
EvalJSRunnable runnable = fevalJSRequests.remove(evalJSRequestID);
runnable.runAsync();
}
return true;
}
}
return super.handleMessage(msg);
Expand Down
17 changes: 15 additions & 2 deletions apidoc/Titanium/UI/WebView.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ methods:
- name: evalJS
summary: |
Evaluates a JavaScript expression inside the context of the web view and
optionally, returns a result.
optionally, returns a result. If a callback function is passed in as second argument,
the evaluation will take place asynchronously and the the callback function will be called with the result.
description: |
The JavaScript expression must be passed in as a string. If you are passing in any objects,
you must serialize them to strings using [stringify](Global).
Expand All @@ -194,14 +195,26 @@ methods:
call returns the empty string:
myWebView.evalJS('return document.title');
The `evalJS` variant with a second callback argument executes asynchronously.
myWebView.evalJS('document.title', function (result) {
// Manipulate the result here
});
returns:
type: String
parameters:
summary: Result of the evaluation. May be null if the asynchronous variant of this method is called.

parameters:
- name: code
summary: JavaScript code as a string. The code will be evaluated inside the web view context.
type: String

- name: callback
summary: Optional callback function for the result. Required on Windows, optional on iOS/Android.
optional: true
type: Callback<String>

- name: goBack
summary: Goes back one entry in the web view's history list, to the previous page.

Expand Down
23 changes: 21 additions & 2 deletions iphone/Classes/TiUIWebViewProxy.m
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,28 @@ - (void)_initWithProperties:(NSDictionary *)properties
[super _initWithProperties:properties];
}

- (NSString *)evalJS:(id)code
- (NSString *)evalJS:(id)args
{
ENSURE_SINGLE_ARG(code, NSString);
KrollCallback *callback = nil;
NSString *code = nil;
ENSURE_ARG_AT_INDEX(code, args, 0, NSString); // Conform to class because that's good practice
if ([args count] > 1) {
ENSURE_ARG_AT_INDEX(callback, args, 1, KrollCallback);
}

// Handle async
if (callback != nil) {

// Spin off a thread that will invoke on main thread and fire callback, then return nil immediately
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSString *result = [(TiUIWebView *)[self view] stringByEvaluatingJavaScriptFromString:code];
[callback call:@[ result ] thisObject:nil];
});
});
return nil;
}

/*
Using GCD either through dispatch_async/dispatch_sync or TiThreadPerformOnMainThread
does not work reliably for evalJS on 5.0 and above. See sample in TIMOB-7616 for fail case.
Expand Down
77 changes: 77 additions & 0 deletions tests/Resources/ti.ui.webview.addontest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Appcelerator Titanium Mobile
* Copyright (c) 2011-Present by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
/* eslint-env mocha */
/* global Ti */
/* eslint no-unused-expressions: "off" */
'use strict';
var should = require('./utilities/assertions'),
utilities = require('./utilities/utilities');

describe('Ti.UI.WebView', function () {
var win,
didFocus = false;
this.slow(2000);
this.timeout(10000);

beforeEach(function () {
didFocus = false;
});

afterEach(function () {
if (win) {
win.close();
}
win = null;
});

it('#evalJS(string, function) - async variant', function (finish) {
var webview,
hadError = false;
win = Ti.UI.createWindow({
backgroundColor: 'blue'
});

webview = Ti.UI.createWebView();

webview.addEventListener('load', function () {
if (hadError) {
return;
}

// FIXME: Android is dumb and assumes no trailing semicolon!
webview.evalJS('Ti.API.info("Hello, World!");"WebView.evalJS.TEST"', function (result) {
try {
if (utilities.isAndroid()) {
should(result).be.eql('"WebView.evalJS.TEST"'); // FIXME: Why the double-quoting?
} else {
should(result).be.eql('WebView.evalJS.TEST');
}

finish();
} catch (err) {
finish(err);
}
});
});
win.addEventListener('focus', function () {
if (didFocus) {
return;
}
didFocus = true;

try {
webview.url = 'ti.ui.webview.test.html';
} catch (err) {
hadError = true;
finish(err);
}
});

win.add(webview);
win.open();
});
});

0 comments on commit 86f5857

Please sign in to comment.