Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion test-app/app/src/main/assets/app/mainpage.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ require("./tests/field-access-test");
require("./tests/byte-buffer-test");
require("./tests/dex-interface-implementation");
require("./tests/testInterfaceImplementation");
require("./tests/testRuntimeImplementedAPIs");
require("./tests/testRuntimeImplementedAPIs");
require("./tests/testsInstanceOfOperator");
59 changes: 59 additions & 0 deletions test-app/app/src/main/assets/app/tests/testsInstanceOfOperator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
describe("instanceof operator tests", function () {
it("should return false for numeric types", function () {
const actual = 5 instanceof com.tns.tests.MyInterface1;
expect(actual).toBe(false);
});

it("should return false for plain javascript classes", function () {
function Polygon() {
}
Polygon.prototype.sayName = function() {
return "polygon";
};
const actual = new Polygon() instanceof com.tns.tests.MyInterface1;
expect(actual).toBe(false);
});

it("should return false for ES6 classes", function () {
class Polygon {
sayName() {
return "polygon";
}
}
const actual = new Polygon() instanceof com.tns.tests.MyInterface1;
expect(actual).toBe(false);
});

it("should return false for null", function () {
const actual = null instanceof com.tns.tests.MyInterface1;
expect(actual).toBe(false);
});

it("should return false for instanceof", function () {
const actual = undefined instanceof com.tns.tests.MyInterface1;
expect(actual).toBe(false);
});

it("should return false for instances that do not implement the interface", function () {
const actual = new java.io.File("/") instanceof com.tns.tests.MyInterface1;
expect(actual).toBe(false);
});

it("should return true for instances that inherit from a base type", function () {
const derivedChild = new com.tns.tests.DerivedChild();
const actual = derivedChild instanceof com.tns.tests.DerivedParent;
expect(actual).toBe(true);
});

it("should return true for instances whose parent class directly implements an interface", function () {
const derivedChild = new com.tns.tests.DerivedChild();
const actual = derivedChild instanceof com.tns.tests.MyInterface2;
expect(actual).toBe(true);
});

it("should return true for instances who have a parent class in the hierarchy implementing an interface", function () {
const derivedChild = new com.tns.tests.DerivedChild();
const actual = derivedChild instanceof com.tns.tests.MyInterface1;
expect(actual).toBe(true);
});
});
6 changes: 6 additions & 0 deletions test-app/app/src/main/java/com/tns/tests/AbstractParent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.tns.tests;

public abstract class AbstractParent implements MyInterface1 {
public void myMethod1() {
}
}
4 changes: 4 additions & 0 deletions test-app/app/src/main/java/com/tns/tests/DerivedChild.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.tns.tests;

public class DerivedChild extends DerivedParent {
}
6 changes: 6 additions & 0 deletions test-app/app/src/main/java/com/tns/tests/DerivedParent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.tns.tests;

public class DerivedParent extends AbstractParent implements MyInterface2 {
public void myMethod2() {
}
}
5 changes: 5 additions & 0 deletions test-app/app/src/main/java/com/tns/tests/MyInterface1.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.tns.tests;

public interface MyInterface1 {
void myMethod1();
}
5 changes: 5 additions & 0 deletions test-app/app/src/main/java/com/tns/tests/MyInterface2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.tns.tests;

public interface MyInterface2 {
void myMethod2();
}
85 changes: 85 additions & 0 deletions test-app/runtime/src/main/cpp/MetadataNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,22 @@ void MetadataNode::PackageGetterCallback(Local<Name> property, const PropertyCal
if (foundChild) {
auto childNode = MetadataNode::GetOrCreateInternal(child.treeNode);
cachedItem = childNode->CreateWrapper(isolate);

uint8_t childNodeType = s_metadataReader.GetNodeType(child.treeNode);
bool isInterface = s_metadataReader.IsNodeTypeInterface(childNodeType);
if (isInterface) {
// For all java interfaces we register the special Symbol.hasInstance property
// which is invoked by the instanceof operator (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance).
// For example:
//
// Object.defineProperty(android.view.animation.Interpolator, Symbol.hasInstance, {
// value: function(obj) {
// return true;
// }
// });
RegisterSymbolHasInstanceCallback(isolate, child, cachedItem);
}

V8SetPrivateValue(isolate, thiz, strProperty, cachedItem);
}
}
Expand Down Expand Up @@ -1800,6 +1816,75 @@ void MetadataNode::SetMissingBaseMethods(Isolate* isolate, const vector<Metadata
}
}

void MetadataNode::RegisterSymbolHasInstanceCallback(Isolate* isolate, MetadataEntry entry, Local<Value> interface) {
if (interface->IsNullOrUndefined()) {
return;
}

JEnv env;
auto className = GetJniClassName(entry);
auto clazz = env.FindClass(className);
if (clazz == nullptr) {
return;
}

auto extData = External::New(isolate, clazz);
auto hasInstanceTemplate = FunctionTemplate::New(isolate, MetadataNode::SymbolHasInstanceCallback, extData);
auto hasInstanceFunc = hasInstanceTemplate->GetFunction();
PropertyDescriptor descriptor(hasInstanceFunc, false);
auto hasInstanceSymbol = Symbol::GetHasInstance(isolate);
interface->ToObject()->DefineProperty(isolate->GetCurrentContext(), hasInstanceSymbol, descriptor);
}

void MetadataNode::SymbolHasInstanceCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
auto length = info.Length();
if (length != 1) {
throw NativeScriptException(string("Symbol.hasInstance must take exactly 1 argument"));
}

auto arg = info[0];
if (arg->IsNullOrUndefined()) {
info.GetReturnValue().Set(false);
return;
}

auto clazz = reinterpret_cast<jclass>(info.Data().As<External>()->Value());

auto isolate = info.GetIsolate();
auto runtime = Runtime::GetRuntime(isolate);
auto objectManager = runtime->GetObjectManager();
auto obj = objectManager->GetJavaObjectByJsObject(arg->ToObject());

if (obj.IsNull()) {
// Couldn't find a corresponding java instance counterpart. This could happen
// if the "instanceof" operator is invoked on a pure javascript instance
info.GetReturnValue().Set(false);
return;
}

JEnv env;
auto isInstanceOf = env.IsInstanceOf(obj, clazz);

info.GetReturnValue().Set(isInstanceOf);
}

std::string MetadataNode::GetJniClassName(MetadataEntry entry) {
std::stack<string> s;
MetadataTreeNode* n = entry.treeNode;
while (n != nullptr && n->name != "") {
s.push(n->name);
n = n->parent;
}

string fullClassName;
while (!s.empty()) {
auto top = s.top();
fullClassName = (fullClassName == "") ? top : fullClassName + "/" + top;
s.pop();
}

return fullClassName;
}

string MetadataNode::TNS_PREFIX = "com/tns/gen/";
MetadataReader MetadataNode::s_metadataReader;
Expand Down
36 changes: 20 additions & 16 deletions test-app/runtime/src/main/cpp/MetadataNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
#include <map>

namespace tns {
class MetadataNode {
class MetadataNode {
public:
static void Init(v8::Isolate* isolate);

Expand Down Expand Up @@ -129,6 +129,10 @@ namespace tns {
static bool GetExtendLocation(std::string& extendLocation, bool isTypeScriptExtend);
static ExtendedClassCacheData GetCachedExtendedClassData(v8::Isolate* isolate, const std::string& proxyClassName);

static void RegisterSymbolHasInstanceCallback(v8::Isolate* isolate, MetadataEntry entry, v8::Local<v8::Value> interface);
static void SymbolHasInstanceCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
static std::string GetJniClassName(MetadataEntry entry);

v8::Local<v8::Function> Wrap(v8::Isolate* isolate, const v8::Local<v8::Function>& function, const std::string& name, const std::string& origin, bool isCtorFunc);

bool CheckClassHierarchy(JEnv& env, jclass currentClass, MetadataTreeNode* curentTreeNode, MetadataTreeNode* baseTreeNode, std::vector<MetadataTreeNode*>& skippedBaseTypes);
Expand All @@ -150,13 +154,13 @@ namespace tns {

struct MethodCallbackData {
MethodCallbackData()
:
node(nullptr), parent(nullptr), isSuper(false) {
:
node(nullptr), parent(nullptr), isSuper(false) {
}

MethodCallbackData(MetadataNode* _node)
:
node(_node), parent(nullptr), isSuper(false) {
:
node(_node), parent(nullptr), isSuper(false) {
}

std::vector<MetadataEntry> candidates;
Expand All @@ -167,8 +171,8 @@ namespace tns {

struct ExtendedClassCallbackData {
ExtendedClassCallbackData(MetadataNode* _node, const std::string& _extendedName, const v8::Local<v8::Object>& _implementationObject, std::string _fullClassName)
:
node(_node), extendedName(_extendedName), fullClassName(_fullClassName) {
:
node(_node), extendedName(_extendedName), fullClassName(_fullClassName) {
implementationObject = new v8::Persistent<v8::Object>(_implementationObject->GetIsolate(), _implementationObject);
}

Expand All @@ -181,17 +185,17 @@ namespace tns {

struct TypeMetadata {
TypeMetadata(const std::string& _name)
:
name(_name) {
:
name(_name) {
}

std::string name;
};

struct CtorCacheData {
CtorCacheData(v8::Persistent<v8::FunctionTemplate>* _ft, std::vector<MethodCallbackData*> _instanceMethodCallbacks)
:
ft(_ft), instanceMethodCallbacks(_instanceMethodCallbacks) {
:
ft(_ft), instanceMethodCallbacks(_instanceMethodCallbacks) {
}

v8::Persistent<v8::FunctionTemplate>* ft;
Expand All @@ -200,12 +204,12 @@ namespace tns {

struct ExtendedClassCacheData {
ExtendedClassCacheData()
:
extendedCtorFunction(nullptr), node(nullptr) {
:
extendedCtorFunction(nullptr), node(nullptr) {
}
ExtendedClassCacheData(const v8::Local<v8::Function>& extCtorFunc, const std::string& _extendedName, MetadataNode* _node)
:
extendedName(_extendedName), node(_node) {
:
extendedName(_extendedName), node(_node) {
extendedCtorFunction = new v8::Persistent<v8::Function>(extCtorFunc->GetIsolate(), extCtorFunc);
}
v8::Persistent<v8::Function>* extendedCtorFunction;
Expand All @@ -220,5 +224,5 @@ namespace tns {

std::map<std::string, MetadataNode::ExtendedClassCacheData> ExtendedCtorFuncCache;
};
};
};
}