How to enable 'for...in' my NativeJavaObject within Rhino? - javascript

I am newbie in rhino.
Currently, I am using Rhino 1.7R framework thru .NET 4 and IKVM.NET.
I exposed several wrapped classes implementing NativeJavaObject using setWrapFractory() API.
public class InterceptNativeObject : NativeJavaObject
{
public InterceptNativeObject()
{
}
public override Object get(String name, Scriptable start)
{
Object res = base.get(name, start);
if (res is NativeJavaMethod)
{
NativeJavaMethod method = (NativeJavaMethod)res;
return new RhinoMethodWrapFunction(method);
}
if (res == org.mozilla.javascript.UniqueTag.NOT_FOUND &&
base.javaObject is IPropertBox && base.javaObject != null)
{
object ret = ((IPropertBox)base.javaObject)._x__GetPropertyValue(name);
return Utils.ConvertCLRValueToJavaValue(ret);
}
return res;
}
.....
}
Now, I can access all .NET methods and properties as I wanted.
My current problem is to support 'for...in' my NativeJavaObject classes.
When I evaluate
'for(var prop in myClass){printf(prop);};' ,
it returns 'no 'in' call for non-object' error.
It seems the 'get' attempting to searching an object of ' _iterator_', but it resulted in 'not found' at get() function. So, it ends up with exception.
So far, I tried
added java.util.iterator
return this.getIds().GetEnumrator();
None of works.
How can i allow property enumrate access for my Wrapped NativeJavaObject?
What is Rhino's expected return value of ' _iterator_' to enable 'for...in'?
Thanks in advance!

__iterator__ is part of a Mozilla specific language extension. That link explains that the __iterator__ method returns an object with a next method that throws StopIteration when the iterator is exhausted.
You have to opt in to iterators and generators though:
To enable JavaScript 1.7 support, you must set the version as 170 using the Context.setLanguageVersion() API call. If you are using the Rhino shell, you can specify -version 170 on the command line or call version(170) in code executed by the shell.
You can write JS in rhino that will wrap a Java iterator to present it as a JS iterator:
function javaIteratorToJsIterator(javaIterator) {
return {
next: function () {
if (!javaIterator.hasNext()) { throw StopIteration; }
return javaIterator.next();
}
};
}
Alternatively, you can use Mozilla style generators but I think you need to create the Rhino interpreter with an option to enable them.
While custom iterators are a useful tool, their creation requires careful programming due to the need to explicitly maintain their internal state. Generators provide a powerful alternative: they allow you to define an iterative algorithm by writing a single function which can maintain its own state.
A generator is a special type of function that works as a factory for iterators. A function becomes a generator if it contains one or more yield expressions.

Related

How to work with a reference to a JS object inside WASM, which is compiled from C++?

I'm trying to develop a C++ library that will work with HTML elements. So, I need classes that will store references to Element, which I will get through document.getElementById("id"). Since I can't work with the DOM directly from WASM, I need to get a reference from the imported JS function somehow in C++ when I call that function. But I don't quite understand what it's supposed to look like.
In the documentation I found a class called WebAssembly.Table. As far as I understand, in this table I can just store references that I can access from WASM. But I don't really understand how to access this table from C++. I haven't yet compiled C++ into WASM, but for now I'm just learning how I can do it. As far as I understand, I can do this with Clang by setting the target platform as WASM.
So, are there any special types in Clang for transferring references from JS? Builtin functions to access WebAssembly.Table?
The pseudocode of what I mean:
Ref DOMGetElementById();
void DOMChangeText(Ref element);
class CMyElement
{
private:
Ref Element;
public:
CMyElement() :
Element{ DOMGetElementById() } { }
void ChangeText()
{
DOMChangeText(Element);
}
};
int main()
{
CMyElement myElement;
myElement.ChangeText();
return 0;
}
const wasmImports =
{
env:
{
DOMGetElementById: () =>
{
return document.getElementById("myElement");
},
DOMChangeText: (element) =>
{
element.textContent = "Hello world."
}
}
};
const wasmModule = new WebAssembly.Module(wasmCode);
const wasmInstance = new WebAssembly.Instance(wasmModule, wasmImports);
console.log(wasmInstance.exports.main());
I understand that the code above most likely cannot be implemented due to the fact that GC has to count references.
Okay, apparently this is why we have to use a class called WebAssembly.Table. But I can't figure out what I should call in C++ to get a value from this table and pass it to the called function.
I imagine that Clang should provide me with some functions or special types that I should use for such a task. But I could not find it.
So I need an explanation of how I can implement my idea.

Define server function for mongo based on pure javascript function

Suppose I have a pure javascript file that has several function definitions. It does not contain anything mongo-specific - these functions parse and evaluate a query on a document. I would like to use these functions for server queries (e.g. db.collection.find(...) or map-reduce). How may I do this so that the server functions can essentially invoke what's in the pure javascript file?
Example: In foobar.js -
function foo(query, doc) {
// some predicate, for now lets simulate by evaluating a boolean field called 'available'
return doc.available;
}
According to https://docs.mongodb.org/manual/tutorial/store-javascript-function-on-server/
I would have to do this:
db.system.js.save(
{
_id : "bar" ,
value : function (query, doc){
return doc.available;
}
}
);
Then invoke db.loadServerScripts();
Running the following gives me the desired results:
db.myCollection.find(function() { return bar("some query", this) ;})
However I'd like to avoid inlining the functions in this db.system.js.save call. These functions belong to a library that I do not maintain. If there are any changes to the javascript library, I would have to manually convert them to db.system.js.save calls.
I would like to run something like this in mongo shell:
load("foobar.js")
db.system.js.save(
{
_id : "baz" ,
value : function (query, doc){
return foo(query, doc);
}
}
);
Then:
db.myCollection.find(function() { return baz("some query", this); })
But I get a "ReferenceError: foo is not defined" in my queries that invoke baz. Function "foo" is clearly not in scope. Suggestions or alternatives?
Disclaimer: I understand that using native operations are preferred, in addition to moving complexity out of the javascript into my application. However I am not at the point where I have the resources to essentially fork the logic from javascript to application logic that decomposes to native ops

Javascript ES6 cross-browser detection

How can I find out the browser's Javascript engine version and support to ECMAScript 6?
I'm using navigator.appVersion just to know the browser's version, but not the engine's version.
Feature detection
I suggest you to use feature detection instead of detecting the browser's engine with heuristic methods. To do this you can simply wrap some code inside a try {..} catch (e) {...} statement, or use some if (...) statements.
For example:
function check() {
if (typeof SpecialObject == "undefined") return false;
try { specialFunction(); }
catch (e) { return false; }
return true;
}
if (check()) {
// Use SpecialObject and specialFunction
} else {
// You cannot use them :(
}
Why is feature detection better than browser/engine detection?
There are multiple reasons that make, in most of the cases, feature detection the best option:
You don't have to rely on browser's version, engine or specifics, nor detect them using heuristic methods which are hard and pretty crafty to implement.
You will not fall into errors regarding browser/engine specifications detection.
You don't have to worry about browser-specific features: for example WebKit browsers have different specifications than other ones.
You can be sure that, once a feature is detected, you'll be able to use it.
These are the main reasons that IMHO make feature detection the best approach.
Feature detection + fallback
When using feature detection, a pretty smart way to work when you aren't sure which features you can/cannot use consists in several feature detections and consequent fallbacks to more basic methods (or even creation of these methods from scratch) in case the features you want to use are not supported.
A simple example of feature detection with fallback may be applied to the window.requestAnimationFrame feature, which is not supported by all the browsers, and has several different prefixes depending on the browser you're working on. In this case, you can easily detect and fallback like this:
requestAnimationFrame =
window.requestAnimationFrame // Standard name
|| window.webkitRequestAnimationFrame // Fallback to webkit- (old versions of Chrome or Safari)
|| window.mozRequestAnimationFrame // Fallback to moz- (Mozilla Firefox)
|| false; // Feature not supported :(
// Same goes for cancelAnimationFrame
cancelAnimationFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || false;
if (!requestAnimationFrame) {
// Not supported? Build it by yourself!
requestAnimationFrame = function(callback) {
return setTimeout(callback, 0);
}
// No requestAnim. means no cancelAnim. Built that too.
cancelAnimationFrame = function(id) {
clearTimeout(id);
}
}
// Now you can use requestAnimationFrame
// No matter which browser you're running
var animationID = requestAnimationFrame(myBeautifulFunction);
ECMAScript 6 (Harmony) features detection
Now, coming to the real problem: if you want to detect the support to ES6, you'll not be able to behave like I said above, because a relevant range of ES6 features is based on new syntaxes and private words, and will throw a SyntaxError if used in ES5, which means that writing a script which contains both ES5 and ES6 is impossible!
Here is an example to demonstrate this issue; the below snippet won't work, and it will be blocked before execution because contains illegal syntax.
function check() {
"use strict";
try { eval("var foo = (x)=>x+1"); }
catch (e) { return false; }
return true;
}
if (check()) {
var bar = (arg) => { return arg; }
// THIS LINE will always throw a SyntaxError in ES5
// even before checking for ES6
// because it contains illegal syntax.
} else {
var bar = function(arg) { return arg; }
}
Now, since that you cannot both check and execute ES6 conditionally in the same script, you'll have to write two different scripts: one which only uses ES5, and another one which includes ES6 features. With two different scripts you'll be able to import the ES6 one only if it is supported, and without causing SyntaxErrors to be thrown.
ES6 detection and conditional execution example
Now let's make a more relatable example, and let's say you want to use these features in your ES6 script:
The new Symbol objects
Classes built with the class keyword
Arrow ((...)=>{...}) functions
NOTE: feature detection of newly introduced syntaxes (like arrow functions) can only be done using the eval() function or other equivalents (e.g. Function()), because writing invalid syntax will stop the script before its execution. This is also the reason why you cannot use if statements to detect classes and arrow functions: these features are regarding keywords and syntax, so an eval(...) wrapped inside a try {...} catch (e) {...} block will work fine.
So, coming to the real code:
HTML Markup:
<html>
<head>
<script src="es5script.js"></script>
</head>
<body>
<!-- ... -->
</body>
</html>
Code in your es5script.js script:
function check() {
"use strict";
if (typeof Symbol == "undefined") return false;
try {
eval("class Foo {}");
eval("var bar = (x) => x+1");
} catch (e) { return false; }
return true;
}
if (check()) {
// The engine supports ES6 features you want to use
var s = document.createElement('script');
s.src = "es6script.js";
document.head.appendChild(s);
} else {
// The engine doesn't support those ES6 features
// Use the boring ES5 :(
}
Code in your es6script.js:
// Just for example...
"use strict";
class Car { // yay!
constructor(speed) {
this.speed = speed;
}
}
var foo = Symbol('foo'); // wohoo!
var bar = new Car(320); // blaze it!
var baz = (name) => { alert('Hello ' + name + '!'); }; // so cool!
Browser/engine detection
Like I said above, browser and engine detection are not the best practices when programming some JavaScript script. I'm gonna give you some background on this topic, just not to leave my words as a "random personal opinion".
Quoting from the MDN Documentation [link]:
When considering using the user agent string to detect which browser is being used, your first step is to try to avoid it if possible. Start by trying to identify why you want to do it.
[...] Are you trying to check for the existence of a specific feature?
Your site needs to use a specific Web feature that some browsers don't yet support, and you want to send those users to an older Web site with fewer features but that you know will work. This is the worst reason to use user agent detection, because odds are eventually all the other browsers will catch up. You should do your best to avoid using user agent sniffing in this scenario, and do feature detection instead.
Also, you're saying you use navigator.appVersion, but consider using another approach, because that one, together with many other navigator properties, is deprecated, and doesn't always behave like you think.
So, quoting from the MDN Documentation [link] again:
Deprecated: this feature has been removed from the Web standards. Though some browsers may still support it, it is in the process of being dropped. Do not use it in old or new projects. Pages or Web apps using it may break at any time.
Note: Do not rely on this property to return the correct browser version. In Gecko-based browsers (like Firefox) and WebKit-based browsers (like Chrome and Safari) the returned value starts with "5.0" followed by platform information. In Opera 10 and newer the returned version does not match the actual browser version, either.
Browser vendors that support ES6 modules now provide an easy way to do feature detection:
...
<head>
<script nomodule>window.nomodules = true;</script>
<script>console.log(window.nomodules)</script>
</head>
...
The script with the nomodule attribute will not be excuted by browsers which support <script type="module" ...>
You can also inject the script like this:
const script = document.createElement('script');
script.setAttribute('nomodule', '');
script.innerHTML = 'window.nomodules = true;';
document.head.insertBefore(script, document.head.firstChild);
script.remove();
As Marco Bonelli said, the best way to detect ECMAScript 6 language syntax is to use eval();. If the call does not throw an error, "all other" features are supported, but I recommend Function();.
function isES6()
{
try
{
Function("() => {};"); return true;
}
catch(exception)
{
return false;
}
}
demo: https://jsfiddle.net/uma4Loq7/
No eval ES6 feature detection
You can do it without using eval - just insert the detection code in its own script block and make a global variable assignment at the end. The variable assignment will not run if any error occurs in the script block.
<script>
window.isES6 = false;
</script>
<script>
// Arrow functions support
() => { };
// Class support
class __ES6FeatureDetectionTest { };
// Object initializer property and method shorthands
let a = true;
let b = {
a,
c() { return true; },
d: [1,2,3],
};
// Object destructuring
let { c, d } = b;
// Spread operator
let e = [...d, 4];
window.isES6 = true;
</script>
<script>
document.body.innerHTML += 'isES6: ' + window.isES6;
</script>
https://jsfiddle.net/s5tqow91/2/
Please note that there are many ES6 features, and checking only one does not guarantee you are covered. (The above code doesn't cover everything either, it's just what I think are the features that I use most often).
Why no eval?
The main reason is security and it's not that calling eval for feature detection is insecure by itself. It's that ideally you should disallow eval with Content Security Policy so it cannot be used at all - which greatly decreases attack surface. But if your own code uses eval, you cannot do that.
Detect devicePixelRatio which is a special property in WebKit.
Detect javaEnabled function's implement.
(function() {
var v8string = 'function%20javaEnabled%28%29%20%7B%20%5Bnative%20code%5D%20%7D';
var es6string = 'function%20javaEnabled%28%29%20%7B%0A%20%20%20%20%5Bnative%20code%5D%0A%7D';
if (window.devicePixelRatio) //If WebKit browser
{
var s = escape(navigator.javaEnabled.toString());
if (s === v8string) {
alert('V099787 detected');
} else if (s === es6string) {
alert('ES6 detected')
} else {
alert('JSC detected');
}
} else {
display("Not a WebKit browser");
}
function display(msg) {
var p = document.createElement('p');
p.innerHTML = msg;
document.body.appendChild(p);
}
})()
For now there's not a exact way to detect ES6, but if you test its features in the current browser, you can determine if the engine is ES6. My esx library detects the ECMAScript version by doing syntax tests and methods check. For know it can detect ECMAScript 3, 5, 6 and 7 (ES7 not tested, but should work), if no ECMAScript test matched, it gives null as result.
Example using my library:
if (esx.detectVersion() >= 6) {
/* We're in ES6 or above */
}
As Damian Yerrick has mentioned, the use of eval() or Function() is incompatible with a Content Security Policy that does not specify 'unsafe-eval'.
If the browser supports Worker then you can detect support for any ES6 syntax by implementing that syntax in a worker and checking for error or successs eg to detect support for arrow functions:
worker.js
// If ES6 arrow functions are supported then the worker listener will receive true, otherwise it will receive an error message
(() => {
postMessage(true);
})();
index.js
if (typeof (Worker) !== "undefined") {
var myWorker = new Worker('worker.js');
myWorker.onmessage = function (e) {
// arrow functions must be supported since we received message from the worker arrow function
}
myWorker.onerror = function (e) {
// the worker triggered an error so arrow function not supported (could explicitly check message for syntax error)
}
}
This function returns true in Chrome 98.0.4758.80 and Firefox 97.0.2 (just tested). It may not work on other browsers and previous versions of Chrome/Firefox (false negative results)
function hasAsyncSupport () {
return Object.getPrototypeOf(async function() {}).constructor.toString().includes('Async')
}
The best way, I think, is to simply write all ES6 scripts as modules, and use nomodule script tags for any fallbacks. Trying to code inline detection is not needed.
Of course, this begs the question of how to detect new syntax from post-ES6 versions, such as the "??" and "?." operators.
Put the incompatible syntax code, such as containing arrow functions, in it's own script block and polyfill it with compatible syntax code.
<script>
// This script block should not compile on incompatible browsers,
// leaving the function name undefined.
// It can then be polyfilled with a function containing compatible syntax code.
function fame() {
/* incompatible syntax code such as arrow functions */
}
</script>
<script>
if (typeof fame !== "function") {
// alert("polyfill: fame");
function fame() {
/* compatible syntax code */
}
}
</script>
<script>
// main code
fame();
</script>

How do you call JavaScript delete from Kotlin?

I am working with a third party library from Kotlin and one of the things I must do is call delete thing[key] in order to remove an item from thing. I am unable to figure out how to do this from Kotlin code.
I did try js("delete thing[key]"), but thing is a parameter to a function and is name-mangled by the Kotlin > JavaScript compiler so an exception is thrown when the line is executed. I also tried js("delete ") thing[key] but unsurprisingly that didn't work either.
For delete operator You can write:
external fun delete(p: dynamic): Boolean = noImpl
//...
delete(thing[key])
For more convenient use I'd added some helpers:
fun delete(thing: dynamic, key: String) {
delete(thing[key])
}
// or
fun String.deleteFrom(d: dynamic) {
delete(d[this])
}
fun test(a: Any, k: String) {
delete(a, k)
k.deleteFrom(a)
k deleteFrom a
}
Note: using the delete operator is not a good practice and it'll leads to deoptimizations in JS VMs

Overriding external object's function using JavaScript in Internet Explorer

I am currently working on an IE-only project which makes use of an external object model provided by the host application. Internet Explorer allows access to this external object through browser components:
http://msdn.microsoft.com/en-us/library/ie/ms535246(v=vs.85).aspx
Access to the object takes the form of JavaScript function invocations, similar to:
external.MethodName(arg1, arg2);
One of the recent changes to the application flow will introduce hundreds, if not thousands of if-statement conditionals around these JavaScript invocations, e.g.:
if (X) {
external.MethodName(arg1, arg2);
} else {
// do something else
}
Rather than modify potentially thousands of HTML files, it would seem to make sense if we could override or rewrite the external object's functions so that the if condition only appears in one place. Normally, this could be accomplished in JavaScript with:
external.OldMethodName = external.MethodName;
external.MethodName = function(arg1, arg2) {
if (X) {
external.OldMethodName(arg1, arg2);
} else {
// do something else
}
};
However, this results in an "Invalid procedure call or argument" script error, because you cannot reference the external host method this way.
I do not have access to the host application proprietary code to change the external method directly.
Is there any way I can use JavaScript to override the external object's functions, or will I need to wrap the (potential) thousands of invocations with if-statements (a very bad practice)?
UPDATE: After much back-and-forth with the client, we have managed to reach out to the third-party vendor to update the external host method, which is vastly preferable to our method of wrapping the method on the front end. I have accepted Paul's answer in the meantime.
Use toString() and eval:
var foo = external.MethodName.toString().replace("OldMethodName", "MethodName").replace("bar","baz");
eval(foo);
if(x)
{
external.OldMethodName(arg1,arg2);
}
else
{
MethodName(arg1,arg2)
}

Categories

Resources