Android, extract javascript variable from webview using javascript interface - javascript

How can I extract this variable bellow from a website to my android code?
I guess it should work using javascript interface but how do I get it?
<script type="text/javascript">
var Ids = "[4161, 104, 121, 202, 1462]";
</script>
And I can't change the code on the website to a method that returns the value.
Any suggestions?

You can use the javascript: scheme in a webview.loadurl call. It will execute the javascript in the webview page.
From there you can make it call a function in your javascript interface.
webview.loadUrl("javascript:Android.getIds(Ids);");
Android being the name space used to declare your javascript interface.
//Add the javascript interface to your web view
this.addJavascriptInterface(new CustomJavaScriptInterface(webViewContext), "Android");
Beware that javascriptinterface only work with primitive types. So you actually can't pass directly an array. Just use the javascript scheme to loop through your array. I see it is not really an array so you should be fine with just :
public class CustomJavaScriptInterface {
Context mContext;
/** Instantiate the interface and set the context */
CustomJavaScriptInterface(Context c) {
mContext = c;
}
/** retrieve the ids */
public void getIds(final String myIds) {
//Do somethings with the Ids
}
}

Related

Send Object from Javascript to Kotlin using Webview

I have loaded a webpage using WebView component and added a JavascriptInterface. Please check the code below,
val webview = WebView(this)
setContentView(webview)
webview.settings.javaScriptEnabled = true
webview.loadUrl(HOME_PAGE_URL)
webview.addJavascriptInterface(JavascriptInterface(),”javascript_bridge”)
And when I call the invoke from Javascript using window.javascript_bridge.showToast(“Information Saved”);
private inner class JavascriptInterface
{
#android.webkit.JavascriptInterface
fun showToast(text: String?)
{
Log.d("WEBVIEW", text);
}
}
I am able to call the method from Javascript to Kotlin without any trouble.
But now I want to pass an Object from Javascript to Kotlin like below,
var info = {
message: “Information Saved”,
ID: 123456
}
And when I call the invoke from Javascript using window.javascript_bridge.showToast(info);
I tried to change to the data type to Any, but the value passed from Javascript is null
private inner class JavascriptInterface
{
#android.webkit.JavascriptInterface
fun showToast(text: Any?)
{
Log.d("WEBVIEW", text.toString());
}
}
As far as i know, the javaScript interface methods only accepts primitive types of data as parameters (as discussed on this question).
If you still want to achieve that, you may serialize the object (to JSON format, for instance) in the javaScript and then Deserialize it in Java.
Hope it helps :)

Passing Android webview JS interface method as callback function

I have the following android code:
private class MyJavaInterface {
#android.webkit.JavascriptInterface
public void call(String number) {
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + number));
startActivity(intent);
}
}
...
mWebView.addJavascriptInterface(new MyJavaInterface(), "MyInterface");
This JS code works correctly:
MyInterface.call(number);
And this does not:
var call = MyInterface.call;
call(number);
What am I doing wrong?
This JS code works correctly:
MyInterface.call(number);
Yes, this will work fine because Android will automatically find the class and the only public methods that are annotated.
For your case
MyInterface.call(number) -> new MyJavaInterface().call(number)
But the Second one will not work,
MyInterface.call -> new MyJavaInterface().call - there is no public variable in your class and also there is no support for variable in addJavascriptInterface
Note : Refer
For applications targeted to API level JELLY_BEAN_MR1 and above, only
public methods that are annotated with JavascriptInterface can be
accessed from JavaScript. For applications targeted to API level
JELLY_BEAN or below, all public methods (including the inherited ones)
can be accessed

How to create an C#.Net DLL to use in JavaScript

I have created a .Net DLL with few simple classes. I have registered the DLL using RegAsm and I got a message that types were registered successfully.
RegAsm Syntax Used :
C:\Windows\Microsoft.NET\Framework\v4.0.30319>RegAsm.exe "D:\Projects\TestDLL.Core.dll"
C# Code:
namespace MyTestDLL.Core
{
public class PacketInfo
{
// constructor
public PacketInfo()
{
}
public string HostName { get; set; }
// and so on ......
}
}
I have set the ComVisible property to true in AssemblyInfo.cs file of this DLL.
// [assembly: ComVisible(true)]
However when I create an object out of it in JavaScript and run the script in Command prompt , I'm getting either it is not an object or null.
JS Code :
var testObj = new ActiveXObject(MyTestDLL.Core.PacketInfo);
testObj.HostName = "Test";
Can anyone advise me how to resolve this ?
You need to add a ProgId attribute to the class, something like this:
[Guid("some guid that you will never change")]
[ProgId("MyLib.PacketInfo")] // this is arbitrary
public class PacketInfo
{
....
}
The guid is optional, but if you don't set it yourself, it will be something you won't control, so it's better to define it. And of course I did not add the ComVisible because you already defined it at assembly level (I personnally prefer to set it at class level).
Then, once registered, you shall be able to use it like this (use the progid as a string):
var testObj = new ActiveXObject('MyLib.PacketInfo');
testObj.HostName = "Test";
I was able to achieve that by adding the following line to My DLL just above the class,
[Guid("A32AB771-9967-4604-B193-57FAA25557D4"), ClassInterface(ClassInterfaceType.None)]
Earlier I wasn't having the ClassInterfaceType part in my code.
Also ensure that each of your classes are having unique GUID.
FYI : You can create GUID using Visual Studio Tools GUID Creator GUI.

How WebBrowser control in .net handles ObjectForScripting

As far as I know we can call C# function from Javascript, that is loaded inside a WebBrowser control, following code shows how I usually do it.
Form1.cs
public partial class Form1 : Form{
private WebBrowser webBrowser1;
public ApplicationWindow(){
InitializeComponent();
WebBrowser webBrowser1 = new WebBrowser();
//some code follows
webBrowser1.ObjectForScripting = new ScriptManager();
this.webBrowser1.Url = new Uri("file:///d:/ui/application.html");
}
}
}
ScriptManager.cs
namespace WindowsFormsApplication10 {
[ComVisible(true)]
public class ScriptManager{
public string GetAllDomains(){
string result=null;
//does something;
return result;
}
}
}
application.html
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
$(document).ready(function () {
var result = window.external.GetAllDomains();
//it works but this is what puzzles me.
});
</script>
The questions that intrigues me are
why we need ComVisible to be true for class whose object we are going to use as objectForScripting?
How Javascript object window.external has the same methods as in objectForScripting?
How they handle cross language type conversion?
I wonder why no one answered for so long. The answer to all your questions is COM - Component Object Model.
Windows is providing ability (using COM) for classes and functions from one program (exe) to be accessible outside the exe.
So
1) why we need ComVisible to be true for class whose object we are going to use as objectForScripting?
-> This tells windows to make the class and its methods visible to the webbrowser.
2) How Javascript object window.external has the same methods as in objectForScripting?
-> The javascript is calling methods of the class made visible in above answer.
3) How they handle cross language type conversion?
-> COM handles the types internally so methods in one programming language can be called from another programming language.

Uncaught TypeError when using a JavascriptInterface

I'm currently displaying a bunch of data to the user as HTML in a webview. I have some links below each entry that should call a method in my app when clicked. The Android WebView's javascript interface seems to be the best (only?) way of handling these things. However, whenever I click the link, I get this error message: ERROR/Web Console(6112): Uncaught TypeError: Object [my namespace]#4075ff10 has no method 'edit' at [base URL]:55
I have the following interface declared:
public class JavaScriptInterface {
Context context;
JavaScriptInterface(Context c) {
context = c;
}
public void edit(String postid) {
Log.d("myApp", "EDIT!");
//do stuff
}
}
I then add it to my WebView:
final WebView threadView = (WebView) findViewById(R.id.webViewThread);
threadView.getSettings().setJavaScriptEnabled(true);
threadView.addJavascriptInterface(new JavaScriptInterface(this), "Android");
And, finally, I call this within my HTML as follows:
<div class="post-actions">
<div class="right">
<a onClick="Android.edit('4312244');">Edit</a>
</div>
</div>
The real kicker is this all works when I'm debugging my app via the emulator or adb connection to my phone. When I build and publish the app, it breaks.
I'm at my wits end. Any help or advice would be greatly appreciated!
Same problem for my 2.3.3 mobile phone.
But as I knew one app that worked and another not, I was not happy with this workaround.
And I find out the differnce of my two apps.
The one with the broken JavaScriptInterface uses Proguard.
After a little search, I find a solution.
Short summary: interface JavascriptCallback, which is implemented by JavaScriptInterface and added rules for Proguard in proguard.conf:
public interface JavascriptCallback {
}
public class JavaScriptInterface implements JavascriptCallback {
Context mContext;
/** Instantiate the interface and set the context */
JavaScriptInterface(Context c) {
mContext = c;
}
/** Show a toast from the web page */
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
}
proguard.cfg:
-keep public class YOURPACKAGENAMEHERE.JavascriptCallback
-keep public class * implements YOURPACKAGENAMEHERE.JavascriptCallback
-keepclassmembers class * implements YOURPACKAGENAMEHERE.JavascriptCallback {
<methods>;
}
So, I'm pleased to say that my problem has been solved. Basically, it's a known bug in Gingerbread, and is present on my 2.3.4 device. After some head scratching, I found this workaround concocted by Jason Shah at PhoneGap. The real kudos for this goes to him as my solution is a slightly modified version of the code in that post.
The WebView
In my onLoad method, I call the following function.
private void configureWebView() {
try {
if (Build.VERSION.RELEASE.startsWith("2.3")) {
javascriptInterfaceBroken = true;
}
} catch (Exception e) {
// Ignore, and assume user javascript interface is working correctly.
}
threadView = (WebView) findViewById(R.id.webViewThread);
threadView.setWebViewClient(new ThreadViewClient());
Log.d(APP_NAME, "Interface Broken? " + javascriptInterfaceBroken.toString());
// Add javascript interface only if it's not broken
iface = new JavaScriptInterface(this);
if (!javascriptInterfaceBroken) {
threadView.addJavascriptInterface(new JavaScriptInterface(this), "Android");
}
}
There are several things going on here.
In contrast with the PhoneGap method, I'm using a startsWith comparison against the version string. This is because Build.VERSION.RELEASE is 2.3.4 on my reference device. Rather than test against all releases in the 2.3 series, I'm comfortable painting all devices with one brushstroke.
javascriptInterface is a bool initialized to false. JavaScriptInterface, instantiated as iface, is the class that normally handles JS events in my WebView.
ThreadViewClient is the meat and potatoes of my implementation. It's where all the logic for handling the workaround occurs.
The WebViewClient
In the class ThreadViewClient (which extends WebViewClient), I first account for the fact that the js handler that Android normally attaches isn't here. This means that, if I want to use the same javascript calls from within my WebView, I need to duplicate the interface. This is accomplished by inserting custom handlers into the content of your website once it has loaded...
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (javascriptInterfaceBroken) {
final String handleGingerbreadStupidity =
"javascript:function shortSignature(id) { window.location='http://MyHandler:shortSignature:'+id; }; "
+ "javascript: function longSignature(text, username, forumnumber,threadnumber,pagenumber,postid) { var sep='[MyHandler]';"
+ "window.location='http://MyHandler:longSignature:' + encodeURIComponent(text + sep + username + sep + forumnumber + sep + threadnumber + sep + pagenumber + sep + postid);};"
+ "javascript: function handler() { this.shortSignature = shortSignature; this.longSignature = longSignature;}; "
+ "javascript: var Android = new handler();";
view.loadUrl(handleGingerbreadStupidity);
}
}
There's a lot to process there. In the javascript, I define an object handler that contains the functions that map to my js interface. An instance of it is then bound to "Android", which is the same interface name as that used by non-2.3 implementation. This allows for re-use of the code rendered within your webview content.
The functions take advantage of the fact that Android allows one to intercept all navigation that occurs within a WebView. In order to communicate with the outside program, they alter the window location to one with a special signature. I'll get into this in a bit.
Another thing I'm doing is concatenating the parameters of functions with more than one parameter. This allows me to reduce the code complexity within the location handler.
The location handler is also placed in ThreadViewClient...
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Method sMethod = null;
Log.d(APP_NAME, "URL LOADING");
if (javascriptInterfaceBroken) {
if (url.contains("MyHandler")) {
StringTokenizer st = new StringTokenizer(url, ":");
st.nextToken(); // remove the 'http:' portion
st.nextToken(); // remove the '//jshandler' portion
String function = st.nextToken();
String parameter = st.nextToken();
Log.d(APP_NAME, "Handler: " + function + " " + parameter);
try {
if (function.equals("shortSignature")) {
iface.shortSignature(parameter);
} else if (function.equals("longSignature")) {
iface.longSignature(parameter);
} else {
if (sMethod == null) {
sMethod = iface.getClass().getMethod(function, new Class[] { String.class });
}
sMethod.invoke(iface, parameter);
}
}
//Catch & handle SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException
return true;
}
}
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
return true;
}
Here I am intercepting all URL load events that occur in the WebView. If the destination URL contains a magic string, the app attempts to parse it to extract out the method call. Rather than using the tokenizer to extract the individual parameters, I'm passing it to version of my longSignature method that can parse and handle it. This is detailed in the final part of this post.
If, by the time it has exited the "javascriptInterfaceBroken" block, execution has not be returned to the caller, this method treats the URL loading action as a normal link clicked event. In the case of my application I don't want to use the WebView for that, so I pass it off to the operating system via the ACTION_VIEW intent.
This is very similar to the implementation on Jason's blog. However I am bypassing reflection for the most part. I was attempting to use the method in the block with reflection to handle all of my bound functions, but due to my JavaScriptInterface being a nested class I was unable to look into it from another. However, since I defined the interface within the main Activity scope, its methods can be called directly.
Handling Concatenated Parameters
Finally, in my JavaScriptInterface, I created a handler to deal with the case of a concatenated parameter...
public void longSignature(String everything) {
try {
everything = URLDecoder.decode(everything, "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.e(APP_NAME, e);
}
final String[] elements = everything.split("\\[MyHandler\\]");
if (elements.length != 6) {
Toast.makeText(getApplicationContext(), "[" + elements.length + "] wrong number of parameters!", Toast.LENGTH_SHORT).show();
}
else {
longSignature(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5]);
}
}
Hooray polymorphism!
And that's my solution! There's a lot of room for improvement, but, for now, this is sufficient. Sorry if some of my conventions have raised your hackles - this is my first Android app and I am unfamiliar with some of the best practices and conventions. Good luck!
You have to annotate (#JavascriptInterface) methods in Java class that you want to make available to JavaScript.
public class JavaScriptInterface {
Context context;
#JavascriptInterface
JavaScriptInterface(Context c) {
context = c;
}
#JavascriptInterface
public void edit(String postid) {
Log.d("myApp", "EDIT!");
//do stuff
} }
Its worked for me. Try out this.
I've taken Jason Shah's and Mr S's implementation as the building block for my fix and improved upon it greatly.
There's just far too much code to put into this comment I'll just link to it.
Details: http://twigstechtips.blogspot.com/2013/09/android-webviewaddjavascriptinterface.html
Source: https://github.com/twig/twigstechtips-snippets/blob/master/GingerbreadJSFixExample.java
Key points are:
Applies to all versions of Gingerbread (2.3.x)
Calls from JS to Android are now synchronous
No longer have to map out interface methods manually
Fixed possibility of string separators breaking code
Much easier to change JS signature and interface names

Categories

Resources