Javascript bridge / upcall to JavaFX (via JSObject.setMember() method) breaks when distributing - javascript

The Problem
I spent several hours trying to determine why my distributed code fails and yet my source code when debugging with the IDE (NetBeans) works without issue. I have found a solution and am posting to help others that might have similar issues.
BTW: I'm a self-taught programmer and might be missing a few fundamental concepts -- feel free to educate me.
Background Information
Using a WebView control within JavaFX application I load a webpage from an html file. I want to use JavaScript to handle the HTML side of things but I also need to freely pass information between Java and JavaScript (both directions). Works great to use the WebEngine.executeScript() method for Java initiated transfers and to use JSObject.setMember() in Java to set up a way for JavaScript to initiate information transfer to Java.
Setting up the link (this way breaks later):
/*Simple example class that gives method for
JavaScript to send text to Java debugger console*/
public static class JavaLink {
public void showMsg(String msg) {
System.out.println(msg);
}
}
...
/*This can be added in the initialize() method of
the FXML controller with a reference to the WebEngine*/
public void initialize(URL url, ResourceBundle rb) {
webE = webView.getEngine();
//Retrieve a reference to the JavaScript window object
JSObject jsObj = (JSObject)webE.executeScript("window");
jsObj.setMember("javaLink", new JavaLink());
/*Now in our JavaScript code we can type javaLink.showMsg("Hello!");
which will send 'Hello!' to the debugger console*/
}
The code above will work great until distributing it and attempting to run the JAR file. After hours of searching and testing different tweaks I finally narrowed the problem down to the JavaLink object itself (I eventually learned that you can use try-catch blocks in JavaScript which enabled me to catch the error: "TypeError: showMsg is not a function...").

The Solution
I found that declaring a global variable to hold an instance of the JavaLink class and passing that as a parameter into the setMember() method fixes it so that the app now runs both in the IDE as well as a standalone JAR:
JavaLink jl;
...
jl = new JavaLink();
//replace anonymous instantiation of JavaLink with global variable
jsObj.setMember("javaLink", jl);
Why!?
I'm guessing this has to do with garbage collection and that the JVM does not keep a reference to JavaLink unless you force it to by declaring a global variable. Any other ideas?

Just to demo that the hypothesis postulated in #DatuPuti's answer appears to be correct. Here's a quick test. Pressing the HTML button increments the counter in the HTML page, and also outputs to the system console. After forcing garbage collection by pressing the "GC" button, the updates to the system console stop:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class WebViewCallbackGCTest extends Application {
private final String HTML =
"<html>"
+ " <head>"
+ " <script>"
+ " var count = 0 ;"
+ " function doCallback() {"
+ " count++ ;"
+ " javaLink.showMsg('Hello world '+count);"
+ " document.getElementById('test').innerHTML='test '+count;"
+ " }"
+ " </script>"
+ " </head>"
+ " <body>"
+ " <div>"
+ " <button onclick='doCallback()'>Call Java</button>"
+ " </div>"
+ " <div id='test'></div>"
+ " </body>"
+ "</html>" ;
#Override
public void start(Stage primaryStage) {
WebView webView = new WebView();
webView.getEngine().loadContent(HTML);
JSObject js = (JSObject) webView.getEngine().executeScript("window");
js.setMember("javaLink", new JavaLink());
Button gc = new Button("GC");
gc.setOnAction(e -> System.gc());
BorderPane root = new BorderPane(webView);
BorderPane.setAlignment(gc, Pos.CENTER);
BorderPane.setMargin(gc, new Insets(5));
root.setBottom(gc);
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
public static class JavaLink {
public void showMsg(String msg) {
System.out.println(msg);
}
}
public static void main(String[] args) {
launch(args);
}
}
Of course, as stated in the other answer, if you declare an instance variable
private JavaLink jl = new JavaLink();
and replace
js.setMember("javaLink", new JavaLink());
with
js.setMember("javaLink", jl);
the problem is fixed, and the updates continue to appear in the console after calling System.gc();.

The solution from #DatuPuti works fine but partially. In my case, I've got multiple nested classes in the JavaLink class. Before doing what #DatuPuti exposes all the nested classes link broke and after applying the exposed solution, only the class being called is the one that keeps the link, all the rest are still breaking. This is the model of my JavaLink class:
public class JavaLink{
public NestedClass1 ClassName1;
public NestedClass2 ClassName2;
//Constructor
public JavaLink(){
ClassName1 = new NestedClass1();
ClassName2 = new NestedClass2();
}
//Nested classes being called from Javascript
public class NestedClass1(){}
public class NestedClass2(){}
}
The link would be created like this:
JavaLink javaLink;
...
javaLink = new JavaLink();
jsObject.setMember("JavaLink", javaLink);
Then, from Javascript I call classes methods like this:
JavaLink.ClassName1.method();
JavaLink.ClassName2.method();
And here comes the probem: When the crash occurs calling ClassName1 methods, ClassName2 unlinks and it's not available anymore using Javascript. The same happens if I the crash occurs while calling ClassName2 methods.
The solution that works for me (in addition to the exposed solution):
Besides declaring JavaLink in the higher scope possible, declaring all the nested classes will make them keep the link. For example (keeping the same reference from the example
class model):
JavaLink javaLink;
JavaLink.NestedClass1 nestedClass1;
JavaLink.NestedClass2 nestedClass2;
javaLink = new JavaLink();
nestedClass1 = javaLink.new NestedClass1();
nestedClass2 = javaLink.new NestedClass2();
//Creating an Object array to store classes instances
Object[] javaLinkClasses = new Object[2];
javaLinkClasses[0] = nestedClass1;
javaLinkClasses[1] = nestedClass2;
jsObject.setMember("JavaLink", javaLinkClasses); //Setting member as an array
And then finally, in order to call methods from nested classes in Javascript, object reallocation is needed, just like this (Javascript):
JavaLink.NestedClass1 = JavaLink[0];
JavaLink.NestedClass2 = JavaLink[1];
//Now we are able to call again methods from nested classes without them unlinking
JavaLink.NestedClass1.method();
I hope this helps people facing the same issue. I'm using Java JDK 1.8 with IntelliJIDEA.

Related

upcall to javafx from javascript

I have a problem. I have two classes in same javafx package. A single html file with javascript at the head section, a java class(extending Application). Now the problem is when i tried to click the button after the page is displayed in the javafx webview, nothing is updated in the webView. Below is the code for the two file. Please i need to know why it isn't working. i have been debugging this problem since 8hrs now, no success. thanks in advance.
java class
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import javafx.application.Application;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class JavaFXApplication25 extends Application {
// inner class
public class Adder
{
public double add(double n, double m)
{
return n + m;
}
}
#Override
public void start(Stage primaryStage) throws URISyntaxException, MalformedURLException {
WebView w = new WebView();
WebEngine e = w.getEngine();
e.setJavaScriptEnabled(true);
e.load(this.getClass().getResource("tester.html").toURI().toURL().toExternalForm());
Scene scene = new Scene(new VBox(w));
primaryStage.setScene(scene);
primaryStage.show();
// make javascript aware of java object
e.getLoadWorker().stateProperty().addListener(
(p, o, n) ->{
if(n == Worker.State.SUCCEEDED){
JSObject b = (JSObject) e.executeScript("window");
b.setMember("adder", new Adder());
}
}
);
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
The html file
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="text/javascript">
function addNum(){
var n1 = Number(document.getElementById('num1').value);
var n2 = Number(document.getElementById('num2').value);
var n3 = adder.add(n1, n2);
document.getElementById('r').innerHTML = n3;
}
</script>
</head>
<body>
<input type="text" id="num1" />
<input type="text" id="num2" />
<p> <span id="r"> </span></p>
<button onclick="addNum()" >Add</button>
</body>
The point is that the programs runs and displays the page, but on pressing the button, nothing is updated on the page
I even tried to make the upcall before loading the html page, yet, no success. Please someone should help check the bug in the code. Thanks once again.
Now below is the output after been run. It shows nothing even after when the Add button is clicked several times! No error message on the standard console, nothing nothing!
output
if(n == Worker.State.SUCCEEDED){
JSObject b = (JSObject) e.executeScript("window");
b.setMember("adder", new Adder());
}
Welcome to the execrable pain in the ass that is DOM event sync between JavaFX and DOM stuff.
The worker state "SUCCEEDED" does not necessarily mean that the DOM is loaded in the page.
The DOM is loaded at document.onLoad() ... and even so it's kind of a can of worms since not all content is loaded by that time (images and stuff).
JQuery has a convenient JQuery(document).ready(function (e){ /*do whatever*/ }) routine you can use to trigger something that lets JavaFX know that it's ready to do stuff in the DOM (basically what you tried to accomplish with the code above [..]Worker.State.SUCCEEDED[..])
Essentially the sequence of events is something like:
1. JavaFX => Worker.state reaches SUCCEEDED STATE
2. DOM ====> Starts converting HTML into DOM components
3. ??? ====> This step is a complete mystery unbeknownst to even the savvy-est of skript kiddies. but i'm pretty sure this is where zuckerberg gets access to your webcam for a few frames. ... so beware!
4. DOM ====> Finishes doing its thing and fires off the onLoad() event which triggers the document.load() listener and associated function.
5. DOM ====> Officially inovkes the first JS function it comes across (not always the one you'd think it'd come across because JS is a magic programming language that doesn't give a shit about threads, or sequencing thereof, and anything goes when it comes JOs)
6. DOM+JS => Continues loading for some reason (i am not kidding)
7. DOM+JS => Some other weird flibbetlygibbletly bullshit done by the DOM and whatever javascript runs inside it ... if you're using timers... good luck.
8. DOM+JS => at this point, jQuery (if used) intercepts the .ready() event listener.
9. DOM+JS => believe me... stuff still isn't completely loaded. (because rampant JS scripts can add DOM components as they see fit and no .ready or .load event listener will ever be called.)
To add insult to injury, this sequence of events can take anywhere between 1ms and 1 minute to reach (8)... it's arbitrary as hell.
I wound up writing code which starts comparing node trees for changes in order to figure out when exactly to start messing with the DOM.... but i digress.
What you want is to make sure you initiate your JS-JavaFX communication protocol once the DOM is ready to run your custom javascript.
Put that shit in document.onLoad(), listen for it inside JavaFX, and then set the adder as you did so already... then hope for the best.
Thanks everyone...After a little research, i was able to come up with an extremely simple solution..Here it goes in steps:
declare a private field (which is an object of the class that contains the public methods to be called from javascript) in the main class.
instantiate the private field
add the member to the web engine after it loads
call the java method from the javascript.
so here goes the updated code
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import javafx.application.Application;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class JavaFXApplication25 extends Application {
// inner class
public class Adder
{
public double add(double n, double m)
{
return n + m;
}
}
// STEP 1: Now declare the private field (COMPULSORY)
private Adder adder;
#Override
public void start(Stage primaryStage) throws URISyntaxException, MalformedURLException {
WebView w = new WebView();
WebEngine e = w.getEngine();
e.setJavaScriptEnabled(true);
e.load(this.getClass().getResource("tester.html").toURI().toURL().toExternalForm());
Scene scene = new Scene(new VBox(w));
primaryStage.setScene(scene);
primaryStage.show();
//STEP 2: instantiate the private js object from step1
adder = new Adder();
// make javascript aware of java object
e.getLoadWorker().stateProperty().addListener(
(p, o, n) ->{
if(n == Worker.State.SUCCEEDED){
JSObject b = (JSObject) e.executeScript("window");
b.setMember("adder", new Adder());
}
}
);
}
/**
#param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
The HTML code still remains the same as before.
//Now, i think there is a bug in this aspect of javafx with jdk 8 and above. I am specifically using jdk 8_126...version. It seems there is automatic destruction of the javascript handler class by the jvm. Fortunately, wrapping the js hanler as a private field in the main class seems to prevent this terrible occurrence.

How to expose JS patch function of Incremental DOM library in GWT app using #JsInterop

I would like to use Incremental DOM library in my GWT app.
https://google.github.io/incremental-dom/#about
As I am coming from the Java world, I struggle with concepts of JavaScript namespaces and modules. I was able to use Closure Compiler with closure version of Incremental DOM (has to be build from sources).
It starts with the following line:
goog.module('incrementaldom');
So if I was to use it in regular JS I would type:
var patch = goog.require('incrementaldom').patch;
And then the patch function would be available in the scope of my code. But how to make it accessible from #JsInterop annotated classes?
I tried something like:
public class IncrementalDom {
#JsMethod(namespace = "incrementaldom", name = "patch")
public static native void patch(Element element, Patcher patcher);
#JsFunction
#FunctionalInterface
public interface Patcher {
void apply();
}
}
But it doesn't work. I get this error in the runtime:
(TypeError) : Cannot read property 'patch' of undefined
So I guess I have to somehow expose the incrementaldom module or at least only the patch method. But I don't know how.
After fighting for the whole day I found the solution. In the goog.module: an ES6 module like alternative to goog.provide document I found the missing information about the role of goog.scope function - required modules are visible only within the scoped call.
I created another Closure JS file named incrementaldom.js:
goog.provide('app.incrementaldom'); // assures creation of namespace
goog.require("incrementaldom");
goog.scope(function() {
var module = goog.module.get("incrementaldom");
var ns = app.incrementaldom;
app.incrementaldom.patch = module.patch;
});
goog.exportSymbol("app.incrementaldom", app.incrementaldom);
And now I can call it from Java code like this:
public class IncrementalDom {
#JsMethod(namespace = "app.incrementaldom", name = "patch")
public static native void patch(Element element, Patcher patcher);
#JsFunction
#FunctionalInterface
public interface Patcher {
void apply();
}
}
Still I have to define every object exported in original module separately in the Closure JS file. Fortunately I only need patch method. I hope one day I will find less cumbersome way for #JsInterop with goog.module :(

JavaFx WebView callback from Javascript failing after Garbage Collection

I am currently working on a JavaFX based application, where users can interact with places that are marked on a world map. To do this, I am using an approach similiar to the one described in http://captaincasa.blogspot.de/2014/01/javafx-and-osm-openstreetmap.html ([1]).
However, I am facing a hard-to-debug problem related to the Javascript callback variable injected to the embedded HTML-page using the WebEngine's setMember() method (see also https://docs.oracle.com/javase/8/javafx/embedded-browser-tutorial/js-javafx.htm ([2]) for an official tutorial).
When running the program for a while, the callback variable is loosing its state unpredictably! To demonstrate this behaviour, I developed a minimal working/failing example. I am using jdk1.8.0_121 64-bit on a Windows 10 machine.
The JavaFx App looks as follows:
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javafx.application.Application;
import javafx.concurrent.Worker.State;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class WebViewJsCallbackTest extends Application {
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
public static void main(String[] args) {
launch(args);
}
public class JavaScriptBridge {
public void callback(String data) {
System.out.println("callback retrieved: " + data);
}
}
#Override
public void start(Stage primaryStage) throws Exception {
WebView webView = new WebView();
primaryStage.setScene(new Scene(new AnchorPane(webView)));
primaryStage.show();
final WebEngine webEngine = webView.getEngine();
webEngine.load(getClass().getClassLoader().getResource("page.html").toExternalForm());
webEngine.getLoadWorker().stateProperty().addListener((observableValue, oldValue, newValue) -> {
if (newValue == State.SUCCEEDED) {
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("javaApp", new JavaScriptBridge());
}
});
webEngine.setOnAlert(event -> {
System.out.println(DATE_FORMAT.format(new Date()) + " alerted: " + event.getData());
});
}
}
The HTML file "page.html" looks as follows:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<!-- use for in-browser debugging -->
<!-- <script type='text/javascript' src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script> -->
<script type="text/javascript">
var javaApp = null;
function javaCallback(data) {
try {
alert("javaApp=" + javaApp + "(type=" + typeof javaApp + "), data=" + data);
javaApp.callback(data);
} catch (e) {
alert("caugt exception: " + e);
}
}
</script>
</head>
<body>
<button onclick="javaCallback('Test')">Send data to Java</button>
<button onclick="setInterval(function(){ javaCallback('Test'); }, 1000)">Send data to Java in endless loop</button>
</body>
</html>
The state of the callback variable javaApp can be observed by clicking on the "Send data to Java in endless loop" button. It will continuously try to run the callback method via javaApp.callback, which produces some logging message in the Java app. Alerts are used as an additional communication channel to back things up (always seems to work and currently used as work-around, but that's not how things are ment to be...).
If everything is working as supposed, each time logging similiar to the following lines should be printed:
callback retrieved: Test
2017/01/27 21:26:11 alerted: javaApp=webviewtests.WebViewJsCallbackTest$JavaScriptBridge#51fac693(type=object), data=Test
However, after a while (anything from 2-7 minutes), no more callbacks are retrieved, but only loggings like the following line are printed:
2017/01/27 21:32:01 alerted: javaApp=undefined(type=object), data=Test
Printing the variable now gives 'undefined' instead of the Java instance path. A strange observation is that the state of javaApp is not truly "undefined". using typeof returnsobject, javaApp === undefined evaluates to false. This is in accordance with the fact that the callback-call does not throw an exception (otherwise, an alert starting with "caugt exception: " would be printed).
Using Java VisualVM showed that the time of failure happens to coincide with the time the Garbage Collector is activated. This can be seen by observing the Heap memory consumption, which drops from approx. 60MB to 16MB due to GC.
What's goining on there? Do you have any idea how I can further debug the issue? I could not find any related know bug...
Thanks a lot for your advice!
PS: the problem was reproduced much faster when including Javascript code to display a world map via Leaflet (cf [1]). Loading or shifting the map most of the time instantly caused the GC to do its job. While debugging this original issue, I traced the problem to the minimal example presented here.
I solved the problem by creating an instance variable bridge in Java that holds the JavaScriptBridge instance sent to Javascript via setMember(). This way, Gargbage Collection of the instance is prevented.
Relevant code snippet:
public class JavaScriptBridge {
public void callback(String data) {
System.out.println("callback retrieved: " + data);
}
}
private JavaScriptBridge bridge;
#Override
public void start(Stage primaryStage) throws Exception {
WebView webView = new WebView();
primaryStage.setScene(new Scene(new AnchorPane(webView)));
primaryStage.show();
final WebEngine webEngine = webView.getEngine();
webEngine.load(getClass().getClassLoader().getResource("page.html").toExternalForm());
bridge = new JavaScriptBridge();
webEngine.getLoadWorker().stateProperty().addListener((observableValue, oldValue, newValue) -> {
if (newValue == State.SUCCEEDED) {
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("javaApp", bridge);
}
});
webEngine.setOnAlert(event -> {
System.out.println(DATE_FORMAT.format(new Date()) + " alerted: " + event.getData());
});
}
Altough the code now works smoothly (also in conjunction with Leaflet), I am still irritated of this unexpected behaviour...
Edit: The explanation for this behaviour is documented since Java 9 (thanks #dsh for your clarifying comment! I was working with Java 8 at the time and unfortunately didn't have this information at hand...)

switching bw Javascript and GWT function

I am working on a project using GWT and d3. i have used d3 in javascript files. Let me explain a bit more . i have a class name AstForm in GWT. in this class i have a function which i have called in my javascript file using following code.it the code works fine for me.
public native void PrepareFunctionsForJS() /*-{
$wnd.ExtractOFFNetWork = this.#org.valcri.asstsrchui.client.AstForm::ExtractOFFNetWork(*);
}-*/;
public void ExtractOFFNetWork(JsArrayMixed args)
{
Window.alert("thisCurrent row and Column is " +
args.getString(0) + " " + args.getString(1)+"OffenderNetwork?");
}
void testfunction ()
{
Window.alert("testfunction)
}
in java script i have used the following code
window.ExtractOFFNetWork(["GWT","JS"]);
my code works fine. i can call the ExtractOFFNetWork in javascript file. however the problem is in the ExtractOFFNetWork function when i call testfunction which is also the member function of the ASTFORM class the programe error saying testfunction is not a function. however when i changed testfunction as static than i can access this function within ExtractOFFNetWork. alternatievly i can also use the testfunction inside ExtractOFFNetWork by creating a separate object of ASTForm as
AstForm my =new AstForm();
my.testfunction();
however i do not want to use either static or separate ASTform object to access member function of ASTForm. i also used this.testfunction() within ExtractOFFNetWork but it also does not work. i would appreciate if any body can help to solve my problem i have spend full day without any success :)
already tried this when calling your PrepareFunctionsForJS(btw by java naming conventions method names start with lowercase letter..)
//assuming that you are calling the prepare function from inside the AstForm
public class AstForm() {
//...
PrepareFunctionsForJS(this);
}
public native void PrepareFunctionsForJS(AstForm instance) /*-{
$wnd.ExtractOFFNetWork = instance.#org.valcri.asstsrchui.client.AstForm::ExtractOFFNetWork(*);
}-*/;

C# class attributes not accessible in Javascript

I want to make a class of mine accessible in JavaScript via a C# WebView-Control.
Therefore I am using the WebView.AddWebAllowedObject method. However if I assign an attribute, it works fine, but if I assign the whole class to get all attributes in js, all of the attributes(and methods btw) are "undefined". I tried everything I found in the www. See the attached code:
//The class I want to make accessible
[AllowForWeb, ComVisible(true)]
[MarshalingBehavior(MarshalingType.Agile)]
public class DeviceInformation
{
public string IPAdress { get; private set; }
public DeviceInformation()
{
IPAdress = GetIPAdress();
}
public string GetDeviceUUID()
{
EasClientDeviceInformation deviceinfo = new EasClientDeviceInformation();
return deviceinfo.Id.ToString();
}
public string GetIPAdress()
{
List<string> ipAddresses = new List<string>();
var hostnames = NetworkInformation.GetHostNames();
foreach (var hn in hostnames)
{
if (hn?.IPInformation != null && (hn.IPInformation.NetworkAdapter.IanaInterfaceType == 71 ||
hn.IPInformation.NetworkAdapter.IanaInterfaceType == 6))
{
string ipadress = hn.DisplayName;
return ipadress;
}
}
return string.Empty;
}
}
Here the objects are initialized.
DeviceInformation devinf = new DeviceInformation();
private void View_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
{
if (args.Uri.Host == "")
{
//win_ipadress has an ipadress as value
view.AddWebAllowedObject("win_ipadress", devinf.IPAdress);
//deviceInformation is initialized as well but I have no access to its attributes
view.AddWebAllowedObject("deviceInformation", devinf);
}
}
That's the way i call it in js:
else if ($.os.ie) {
myIpAdr = window.win_ipadress;
//Throws an exception because GetIPAdress() is "undefined"
myIpAdr = window.deviceInformation.GetIPAdress();
}
I am using this in a Windows Universal App. The Javascript and in the WebView displayed HTML-Code is already in use for Android an iOS.
I believe you need to define the method name starting with a lower case character.
For example: change GetIPAddress to getIPAddress.
I tested it on my side and found if I use the upper case name 'GetIPAddress', it won't work. But if I use getIPAddress, it works.
And after I read kangax's explanation in this thread, I think it makes sense.
[Update]
Since it still doesn't work after you make the change on method name, I think the issue should be related to how you expose the windows runtime object. I guess you simply defined the DeviceInformation class and tried to use it in the same project.
First, we need to create a separate windows universal windows runtime component project.
The c# class DeviceInformation should be put into this project. Keep the same code.
Then, in your universal app project, add reference to the windows runtime component and keep rest code to consume the windows runtime object.
[Update 2]
Just noticed an interesting behavior in VS. No matter if the Method name we defined in C# is starting with uppercase or lowercase, the visual studio intellisense shows the lowercase, so the method name will be automatically converted when we try to use it in js.

Categories