GWT and JSInterop namespace - javascript

I've got some native JS test code here (mydialog.js)
var MyDialog = {
foo : function()
{
console.log("foo");
}
};
I'm injecting using the following code using GWT:
ScriptInjector.fromUrl("mydialog.js").setCallback(new Callback<Void, Exception>()
{
#Override
public void onFailure(Exception reason)
{
Window.alert("Dialog Injection Failed!" + reason);
}
#Override
public void onSuccess(Void result) {}
}).inject();
}
Then, I'm trying to set up a JSInterop class here:
#JsType(isNative=true, namespace=JsPackage.GLOBAL, name="MyDialog")
public class MyDialog
{
public static native void foo();
}
The problem, is that the namesoace JsPackage.GLOBAL isn't accurate. The injected code doesn't live under the global namespace, but rather the one generated by GWT and presumably inside the GWT iframe I believe. What is the namespace I need?
In other words, what should this be:
...namespace=???...

JsInterop assumes that the code it is reasoning about lives in the main window - this isn't the difference of a namespace, but a different global context that it runs under (with different Object, Array types, etc). In Java terms you might consider this not just "wrong package", but "wrong classloader", though in a way that you can't correct very nicely.
Instead, direct the ScriptInjector to put your created JS into the main page, and you can interact with it directly with setWindow:
ScriptInjector.fromUrl(...)
.setWindow(ScriptInjector.TOP_WINDOW)
.setCallback(...);
Alternatively, you can use the "magic" string of "<window>", which will mean "don't provide any namespace at all". This isn't an official part of JsInterop (the constant isn't declared in the jsinterop annotations themselves), but at least presently is a way you can work around this.

Related

EntryPointNotFoundException external JS Lib error

Trying to implement JS library with my C# code.
Is very simple but I am getting this error:
EntryPointNotFoundException: Test
TalkDB.Start () (at Assets/Scripts/TalkDB.cs:30)
C# code is in scripts folder and JS library at plugins/webgl with .jslib extension.
Also read this article, but no idea what I am missing: https://docs.unity3d.com/Manual/webgl-interactingwithbrowserscripting.html?_ga=1.27144893.1658401563.1487328483
C# Code:
public class TalkDB : MonoBehaviour
{
[DllImport("__Internal")]
private static extern void Test();
void Start()
{
Test();
}
}
JS Library:
var HighscorePlugin = {
Test: function()
{
window.alert("Testing 1, 2, 3,...");
}
};
mergeInto(LibraryManager.library, HighscorePlugin);
Found the answer, is quite simple in fact.
I does not work when running locally, just when running from a server.
To prevent this error this should be done:
#if UNITY_WEBGL && !UNITY_EDITOR
[DllImport("__Internal")]
private static extern void Test();
#else
// something else to emulate what you want to do
#endif
And do this also when calling the function.
Happy programming :)
EntryPointNotFoundExceptionmeans means that the function "Test" is either (A) not marked as exportable (not visible) or (B) signature does not match C# definition.
Most likely your issue is the former (A).
I would recommend running DUMPBIN.EXE against your library to verify your "test" function is getting exported and that the its respective signature matches your C# definition. Could be some code-injection on JS-side.

Pass object from Javascript to C++/CX based on C++/CX interface - Windows Runtime Components

I'm new to Windows Runtime Component, and have been trying to figure out how to achieve the following.
The C++ interface I want to extend from in Javascript.
namespace MySDK {
public interface class LoggerPlugin
{
public:
virtual void Log (Platform::String^ Tag, Platform::String^ Messsage);
};
}
The C++
namespace MySDK {
public ref class Logger sealed : public Platform::Object
{
public:
static Logger^ GetInstance ();
void SetPlugin (LoggerPlugin^ Plugin);
};
}
What I tried, may seem silly, but I have no idea how to achieve it.
var plugin = {
log: function(tag, message) {
console.log(tag + ':' + message);
}
};
MySdk.Logger.getInstance().setPlugin(plugin);
The error that I get is
JavaScript runtime error: Type mismatch
I couldn't find any documentation or examples on how to achieve this, will appreciate if anyone could provide me an example of how this can be done.
JavaScript cannot implement WinRT interfaces. If you want to have a JavaScript implementation of your plugin, then you will need to build a concrete type that raises events (that JavaScript can subscribe to) rather than defining virtual methods (that C++ or C# could implement).

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

Storing complex GWT-Types as Javascript

In my first GWT module I want to store a JavaScript object, later on I want to receive this object in my second GWT module.
Everything works fine for primitive types, but my complex type will always have all fields set to "undefined".
My class, that I want to transfer from one module to the other:
public class SomeThing {
public Set<String> strings = new HashSet<String>();
}
The entry point of my first module looks like this:
public class EntryA implements EntryPoint {
#Override
public void onModuleLoad() {
// define test data
SomeThing someThing = new SomeThing();
someThing.strings.add("hallo123");
// save data to JavaScript
saveToJavaScript(someThing);
// read and show saved data
Window.alert("ModuleA:"+readFromJavaScript());
Window.alert("ModuleA strings:"+readFromJavaScript().strings);
}
private native void saveToJavaScript(SomeThing thing) /*-{
$wnd.storedThing = thing;
}-*/;
private native SomeThing readFromJavaScript() /*-{
return $wnd.storedThing;
}-*/;
}
The entry point of my second module looks like this:
public class EntryB implements EntryPoint {
#Override
public void onModuleLoad() {
// run delayed, so that ModuleA will be executed first
new Timer() {
#Override
public void run() {
// read and show saved data
Window.alert("ModuleB:"+readFromJavaScript());
Window.alert("ModuleB strings:"+readFromJavaScript().strings);
}
}.schedule(5000);
}
private native SomeThing readFromJavaScript() /*-{
return $wnd.storedThing;
}-*/;
}
I am compiling each module separately. Both generated JavaScript files are included in one html file.
The output is:
ModuleA:moduleA.client.SomeThing#a
ModuleA strings:[hallo123]
ModuleB:moduleA.client.SomeThing#a
ModuleB strings:undefined
Does anyone have an idea how to store such complex types? Let me know, if you need some more information.
UPDATE
I found out, that it actually works, if I am "refreshing" the fields in JavaScript. I have no idea why this works!
private native SomeThing readFromJavaScript() /*-{
var a = $wnd.storedThing;
a.#moduleA.client.SomeThing::strings = a['moduleA_client_SomeThing_strings'];
return $wnd.storedThing;
}-*/;
Nevertheless I need a generic approach, which allows to transfer any object - and I don't want to have to mention every possible field... :(
Maybe this has something to do the way modules are loaded.
The preferred way to load multiple modules it is described in: Loading multiple modules in an HTML host page:
If you have multiple GWT modules in your application, there are two ways to
approach loading them.
1. Compile each module separately and include each module with a separate
<script> tag in your HTML host page.
2. Create a top level module XML definition that includes all the modules you
want to include. Compile the top level module to create a single set of
JavaScript output.
...[cut for brevity] The second approach is strongly recommended.
The reason that you can't read fields between GWT modules is that each module is compiled and obfuscated independently. This means that SomeThing.strings could be mapped to .a in one module and `.q' in another. Your "refresh" trick only works because compiling the module in detailed mode usually results in the same name.
You might want to consider using the AutoBeans framework, which supports JSON-encoding the objects in a stable manner.

Categories

Resources