1 /*
   2  * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
   3  */
   4 
   5 #include "config.h"
   6 
   7 #include "BridgeUtils.h"
   8 
   9 #include <JavaScriptCore/CallFrame.h>
  10 #include <JavaScriptCore/Identifier.h>
  11 
  12 #include "Frame.h"
  13 #include "JavaInstanceJSC.h"
  14 #include "JavaArrayJSC.h"
  15 #include "JavaRuntimeObject.h"
  16 #include "JNIUtilityPrivate.h"
  17 #include "JSDOMBinding.h"
  18 #include "JSDOMGlobalObject.h"
  19 #include "JSMainThreadExecState.h"
  20 #include "JSNode.h"
  21 #include "ScriptController.h"
  22 #include "runtime_array.h"
  23 #include "runtime_object.h"
  24 #include "runtime_root.h"
  25 #include <runtime/JSArray.h>
  26 #include <runtime/JSLock.h>
  27 #include <wtf/java/JavaRef.h>
  28 #include <wtf/text/WTFString.h>
  29 #include <APICast.h>
  30 #include <API/OpaqueJSString.h>
  31 #include <API/JSBase.h>
  32 #include <API/JSStringRef.h>
  33 
  34 #include "com_sun_webkit_dom_JSObject.h"
  35 
  36 #if 1
  37 #define FIND_CACHE_CLASS(ENV, SIG)      \
  38   static JGClass cls((ENV)->FindClass(SIG)); return cls
  39 #else
  40 #define FIND_CACHE_CLASS(ENV, SIG)      \
  41   static jclass cls = (jclass) (ENV)->NewGlobalRef((ENV)->FindClass(SIG)); \
  42   return cls
  43 #endif
  44 
  45 static jclass getJSObjectClass (JNIEnv *env)
  46 {
  47     FIND_CACHE_CLASS(env, "com/sun/webkit/dom/JSObject");
  48 }
  49 
  50 static jclass getJSExceptionClass (JNIEnv *env)
  51 {
  52     FIND_CACHE_CLASS(env, "netscape/javascript/JSException");
  53 }
  54 
  55 static jclass getNodeImplClass (JNIEnv *env)
  56 {
  57     FIND_CACHE_CLASS(env, "com/sun/webkit/dom/NodeImpl");
  58 }
  59 
  60 static jclass getNumberClass (JNIEnv *env)
  61 {
  62     FIND_CACHE_CLASS(env, "java/lang/Number");
  63 }
  64 
  65 static jclass getDoubleClass (JNIEnv *env)
  66 {
  67     FIND_CACHE_CLASS(env, "java/lang/Double");
  68 }
  69 
  70 static jclass getIntegerClass (JNIEnv *env)
  71 {
  72     FIND_CACHE_CLASS(env, "java/lang/Integer");
  73 }
  74 
  75 static jclass getBooleanClass (JNIEnv *env)
  76 {
  77     FIND_CACHE_CLASS(env, "java/lang/Boolean");
  78 }
  79 
  80 static jclass getStringClass (JNIEnv *env)
  81 {
  82     FIND_CACHE_CLASS(env, "java/lang/String");
  83 }
  84 
  85 static jclass getNullPointerExceptionClass (JNIEnv *env)
  86 {
  87     FIND_CACHE_CLASS(env, "java/lang/NullPointerException");
  88 }
  89 
  90 static void throwNullPointerException (JNIEnv *env)
  91 {
  92     jclass clJSException = getNullPointerExceptionClass(env);
  93     env->Throw((jthrowable) env->NewObject(clJSException,
  94             env->GetMethodID(clJSException, "<init>", "()V")));
  95 }
  96 
  97 namespace WebCore {
  98 
  99 JSGlobalContextRef getGlobalContext(WebCore::ScriptController* scriptController)
 100 {
 101     return toGlobalRef(scriptController->globalObject(WebCore::mainThreadNormalWorld())->globalExec());
 102 }
 103 
 104 JSStringRef asJSStringRef(JNIEnv *env, jstring str)
 105 {
 106     unsigned int slen = env->GetStringLength(str);
 107     const jchar* schars = env->GetStringCritical(str, NULL);
 108     JSStringRef name = JSStringCreateWithCharacters((const JSChar*) schars, slen);
 109     env->ReleaseStringCritical(str, schars);
 110     return name;
 111 }
 112 
 113 JSValueRef Java_Object_to_JSValue(
 114     JNIEnv *env,
 115     JSContextRef ctx,
 116     JSC::Bindings::RootObject* rootObject,
 117     jobject val,
 118     jobject accessControlContext)
 119 {
 120     if (val == NULL)
 121         return JSValueMakeNull(ctx);
 122     JSC::ExecState* exec = toJS(ctx);
 123     JSC::JSLockHolder lock(exec);
 124 
 125     jclass clJSObject = getJSObjectClass(env);
 126     if (env->IsInstanceOf(val, clJSObject)) {
 127         static jfieldID fldPeer = env->GetFieldID(clJSObject, "peer", "J");
 128         static jfieldID fldPeerType = env->GetFieldID(clJSObject, "peer_type", "I");
 129         jlong peer = env->GetLongField(val, fldPeer);
 130         jint peer_type = env->GetIntField(val, fldPeerType);
 131         switch (peer_type) {
 132         case com_sun_webkit_dom_JSObject_JS_CONTEXT_OBJECT:
 133             return static_cast<JSObjectRef>(jlong_to_ptr(peer));
 134         case com_sun_webkit_dom_JSObject_JS_DOM_NODE_OBJECT:
 135         case com_sun_webkit_dom_JSObject_JS_DOM_WINDOW_OBJECT:
 136             {
 137                 JSDOMGlobalObject* globalObject = toJSDOMGlobalObject(
 138                     ((peer_type == com_sun_webkit_dom_JSObject_JS_DOM_WINDOW_OBJECT)
 139                         ? static_cast<DOMWindow*>(jlong_to_ptr(peer))->document()
 140                         : &static_cast<Node*>(jlong_to_ptr(peer))->document()),
 141                     exec);
 142                 return toRef(exec,
 143                     (peer_type == com_sun_webkit_dom_JSObject_JS_DOM_WINDOW_OBJECT)
 144                         ? WebCore::toJS(exec, globalObject, static_cast<DOMWindow*>(jlong_to_ptr(peer)))
 145                         : WebCore::toJS(exec, globalObject, static_cast<Node*>(jlong_to_ptr(peer))));
 146             }
 147         }
 148     }
 149     jclass clString = getStringClass(env);
 150     if (env->IsInstanceOf(val, clString)) {
 151         JSStringRef value = asJSStringRef(env, (jstring) val);
 152         JSValueRef jsvalue = JSValueMakeString(ctx, value);
 153         JSStringRelease(value);
 154         return jsvalue;
 155     }
 156     jclass clBoolean = getBooleanClass(env);
 157     if (env->IsInstanceOf(val, clBoolean)) {
 158         static jmethodID booleanValueMethod = env->GetMethodID(clBoolean, "booleanValue", "()Z");
 159         jboolean value = env->CallBooleanMethod(val, booleanValueMethod);
 160         return JSValueMakeBoolean(ctx, value);
 161     }
 162     jclass clNumber = getNumberClass(env);
 163     if (env->IsInstanceOf(val, clNumber)) {
 164         static jmethodID doubleValueMethod = env->GetMethodID(clNumber, "doubleValue", "()D");
 165         jdouble value = env->CallDoubleMethod(val, doubleValueMethod);
 166         return JSValueMakeNumber(ctx, value);
 167     }
 168 
 169     JLObject valClass(JSC::Bindings::callJNIMethod<jobject>(val, "getClass", "()Ljava/lang/Class;"));
 170     if (JSC::Bindings::callJNIMethod<jboolean>(valClass, "isArray", "()Z")) {
 171         JLString className((jstring)JSC::Bindings::callJNIMethod<jobject>(valClass, "getName", "()Ljava/lang/String;"));
 172         const char* classNameC = JSC::Bindings::getCharactersFromJString(className);
 173         JSC::JSValue arr = JSC::Bindings::JavaArray::convertJObjectToArray(exec, val, classNameC, rootObject, accessControlContext);
 174         JSC::Bindings::releaseCharactersForJString(className, classNameC);
 175         return toRef(exec, arr);
 176     }
 177     else {
 178         // All other Java Object types including java.lang.Character will be wrapped inside JavaInstance.
 179         PassRefPtr<JSC::Bindings::JavaInstance> jinstance = JSC::Bindings::JavaInstance::create(val, rootObject, accessControlContext);
 180         return toRef(jinstance->createRuntimeObject(exec));
 181     }
 182 }
 183 
 184 jstring JSValue_to_Java_String(JSValueRef value, JNIEnv* env, JSContextRef ctx)
 185 {
 186     JSStringRef str = JSValueToStringCopy(ctx, value, NULL);
 187     size_t slen = JSStringGetLength(str);
 188     const JSChar* schars = JSStringGetCharactersPtr(str);
 189     jstring result = env->NewString((const jchar*) schars, slen);
 190     JSStringRelease(str);
 191     return result;
 192 }
 193 
 194 jobject JSValue_to_Java_Object(
 195     JSValueRef value,
 196     JNIEnv*,
 197     JSContextRef ctx,
 198     JSC::Bindings::RootObject* rootObject)
 199 {
 200     JSC::ExecState* exec = toJS(ctx);
 201     return convertValueToJValue(exec, rootObject, toJS(exec, value),
 202         JSC::Bindings::JavaTypeObject, "java.lang.Object").l;
 203 }
 204 
 205 static void throwJavaException(
 206     JNIEnv* env,
 207     JSContextRef ctx,
 208     JSValueRef exception,
 209     JSC::Bindings::RootObject* rootObject)
 210 {
 211     jclass clJSObject = getJSObjectClass(env);
 212     jobject jex = JSValue_to_Java_Object(exception, env, ctx, rootObject);
 213     static jmethodID makeID =
 214         env->GetStaticMethodID(clJSObject, "fwkMakeException",
 215                         "(Ljava/lang/Object;)Lnetscape/javascript/JSException;");
 216 
 217     env->Throw(JLocalRef<jthrowable>((jthrowable)env->CallStaticObjectMethod(
 218             clJSObject,
 219             makeID,
 220             jex)));
 221 }
 222 
 223 jobject executeScript(
 224     JNIEnv* env,
 225     JSObjectRef object,
 226     JSContextRef ctx,
 227     JSC::Bindings::RootObject *rootObject,
 228     jstring str)
 229 {
 230     if (str == NULL) {
 231         throwNullPointerException(env);
 232         return NULL;
 233     }
 234     JSStringRef script = asJSStringRef(env, str);
 235     JSValueRef exception = 0;
 236     JSValueRef value = JSEvaluateScript(ctx, script, object, NULL, 1, &exception);
 237     JSStringRelease(script);
 238     if (exception) {
 239         throwJavaException(env, ctx, exception, rootObject);
 240         return NULL;
 241     }
 242     return WebCore::JSValue_to_Java_Object(value, env, ctx, rootObject);
 243 }
 244 
 245 }
 246 
 247 
 248 PassRefPtr<JSC::Bindings::RootObject> checkJSPeer(
 249     jlong peer,
 250     jint peer_type,
 251     JSObjectRef &object,
 252     JSContextRef &context)
 253 {
 254     JSC::Bindings::RootObject *rootObject = NULL;
 255     switch (peer_type) {
 256     case com_sun_webkit_dom_JSObject_JS_CONTEXT_OBJECT:
 257         {
 258             //based on [JavaJSObject] implementation
 259             //[Source/WebCore/bridge/jni/jni_jsobject.mm]
 260             object = static_cast<JSObjectRef>(jlong_to_ptr(peer));
 261             rootObject = JSC::Bindings::findProtectingRootObject(reinterpret_cast<JSC::JSObject*>(object));
 262             if (rootObject) {
 263                 context = toRef(rootObject->globalObject()->globalExec());
 264             }
 265         }
 266         break;
 267     case com_sun_webkit_dom_JSObject_JS_DOM_NODE_OBJECT:
 268     case com_sun_webkit_dom_JSObject_JS_DOM_WINDOW_OBJECT:
 269         {
 270             WebCore::Frame* frame = (peer_type == com_sun_webkit_dom_JSObject_JS_DOM_WINDOW_OBJECT)
 271                 ? static_cast<WebCore::DOMWindow*>(jlong_to_ptr(peer))->document()->frame()
 272                 : static_cast<WebCore::Node*>(jlong_to_ptr(peer))->document().frame();
 273 
 274             rootObject = frame->script().createRootObject(frame).leakRef();
 275             if (rootObject) {
 276                 context = WebCore::getGlobalContext(&frame->script());
 277                 JSC::ExecState* exec = toJS(context);
 278                 JSC::JSLockHolder lock(exec);
 279 
 280                 object = const_cast<JSObjectRef>(toRef(exec,
 281                     (peer_type == com_sun_webkit_dom_JSObject_JS_DOM_WINDOW_OBJECT)
 282                     ? WebCore::toJS(exec, static_cast<WebCore::JSDOMGlobalObject *>(rootObject->globalObject()), static_cast<WebCore::DOMWindow*>(jlong_to_ptr(peer)))
 283                     : WebCore::toJS(exec, static_cast<WebCore::JSDOMGlobalObject *>(rootObject->globalObject()), static_cast<WebCore::Node*>(jlong_to_ptr(peer)))));
 284 
 285             }
 286         }
 287         break;
 288     };
 289 
 290     return rootObject;
 291 }
 292 
 293 
 294 
 295 extern "C" {
 296 
 297 JNIEXPORT jobject JNICALL Java_com_sun_webkit_dom_JSObject_evalImpl
 298 (JNIEnv *env, jclass, jlong peer, jint peer_type, jstring str)
 299 {
 300     if (str == NULL) {
 301         throwNullPointerException(env);
 302         return NULL;
 303     }
 304     JSObjectRef object;
 305     JSContextRef ctx;
 306     RefPtr<JSC::Bindings::RootObject> rootObject(checkJSPeer(peer, peer_type, object, ctx));
 307 
 308     return WebCore::executeScript(env, object, ctx, rootObject.get(), str);
 309 }
 310 
 311 JNIEXPORT jobject JNICALL Java_com_sun_webkit_dom_JSObject_getMemberImpl
 312 (JNIEnv *env, jclass, jlong peer, jint peer_type, jstring str)
 313 {
 314     if (str == NULL) {
 315         throwNullPointerException(env);
 316         return NULL;
 317     }
 318     JSObjectRef object;
 319     JSContextRef ctx;
 320     RefPtr<JSC::Bindings::RootObject> rootObject(checkJSPeer(peer, peer_type, object, ctx));
 321 
 322     JSStringRef name = WebCore::asJSStringRef(env, str);
 323     JSValueRef value = JSObjectGetProperty(ctx, object, name, NULL);
 324     JSStringRelease(name);
 325     return WebCore::JSValue_to_Java_Object(value, env, ctx, rootObject.get());
 326 }
 327 
 328 JNIEXPORT void JNICALL Java_com_sun_webkit_dom_JSObject_setMemberImpl
 329 (JNIEnv *env, jclass, jlong peer, jint peer_type, jstring str, jobject value, jobject accessControlContext)
 330 {
 331     if (str == NULL) {
 332         throwNullPointerException(env);
 333         return;
 334     }
 335     JSObjectRef object;
 336     JSContextRef ctx;
 337     RefPtr<JSC::Bindings::RootObject> rootObject(checkJSPeer(peer, peer_type, object, ctx));
 338 
 339     JSStringRef name = WebCore::asJSStringRef(env, str);
 340     JSValueRef jsvalue = WebCore::Java_Object_to_JSValue(env, ctx, rootObject.get(), value, accessControlContext);
 341     JSPropertyAttributes attributes = 0;
 342     JSValueRef exception = 0;
 343     JSObjectSetProperty(ctx, object, name, jsvalue, attributes, &exception);
 344     JSStringRelease(name);
 345     if (exception)
 346         WebCore::throwJavaException(env, ctx, exception, rootObject.get());
 347 }
 348 
 349 JNIEXPORT void JNICALL Java_com_sun_webkit_dom_JSObject_removeMemberImpl
 350 (JNIEnv *env, jclass, jlong peer, jint peer_type, jstring str)
 351 {
 352     if (str == NULL) {
 353         throwNullPointerException(env);
 354         return;
 355     }
 356     JSObjectRef object;
 357     JSContextRef ctx;
 358     checkJSPeer(peer, peer_type, object, ctx);
 359 
 360     JSStringRef name = WebCore::asJSStringRef(env, str);
 361     JSObjectDeleteProperty(ctx, object, name, NULL);
 362     JSStringRelease(name);
 363 }
 364 
 365 JNIEXPORT jobject JNICALL Java_com_sun_webkit_dom_JSObject_getSlotImpl
 366   (JNIEnv *env, jclass, jlong peer, jint peer_type, jint index)
 367 {
 368     JSObjectRef object;
 369     JSContextRef ctx;
 370     RefPtr<JSC::Bindings::RootObject> rootObject(checkJSPeer(peer, peer_type, object, ctx));
 371 
 372     JSValueRef value = JSObjectGetPropertyAtIndex(ctx, object, index, NULL);
 373     return WebCore::JSValue_to_Java_Object(value, env, ctx, rootObject.get());
 374 }
 375 
 376 JNIEXPORT void JNICALL Java_com_sun_webkit_dom_JSObject_setSlotImpl
 377 (JNIEnv *env, jclass, jlong peer, jint peer_type, jint index, jobject value, jobject accessControlContext)
 378 {
 379     JSObjectRef object;
 380     JSContextRef ctx;
 381     RefPtr<JSC::Bindings::RootObject> rootObject(checkJSPeer(peer, peer_type, object, ctx));
 382 
 383     JSValueRef jsvalue = WebCore::Java_Object_to_JSValue(env, ctx, rootObject.get(), value, accessControlContext);
 384     JSObjectSetPropertyAtIndex(ctx, object, (unsigned) index, jsvalue, NULL);
 385 }
 386 
 387 JNIEXPORT jstring JNICALL Java_com_sun_webkit_dom_JSObject_toStringImpl
 388 (JNIEnv *env, jclass, jlong peer, jint peer_type)
 389 {
 390     JSObjectRef object;
 391     JSContextRef ctx;
 392     checkJSPeer(peer, peer_type, object, ctx);
 393 
 394     JSC::ExecState* exec = toJS(ctx);
 395 
 396     return toJS(object)->toString(exec)->value(exec)
 397         .toJavaString(env).releaseLocal();
 398 }
 399 
 400 JNIEXPORT jobject JNICALL Java_com_sun_webkit_dom_JSObject_callImpl
 401   (JNIEnv *env, jclass, jlong peer, jint peer_type, jstring methodName, jobjectArray args, jobject accessControlContext)
 402 {
 403     if (methodName == NULL || args == NULL) {
 404         throwNullPointerException(env);
 405         return NULL;
 406     }
 407     JSObjectRef object;
 408     JSContextRef ctx;
 409     RefPtr<JSC::Bindings::RootObject> rootObject(checkJSPeer(peer, peer_type, object, ctx));
 410     if (!rootObject || !ctx) {
 411         env->ThrowNew(getJSExceptionClass(env), "Invalid function reference");
 412         return NULL;
 413     }
 414 
 415     JSStringRef name = WebCore::asJSStringRef(env, methodName);
 416     JSValueRef member = JSObjectGetProperty(ctx, object, name, NULL);
 417     JSStringRelease(name);
 418     if (!JSValueIsObject(ctx, member))
 419         return JSC::Bindings::convertUndefinedToJObject();
 420     JSObjectRef function = JSValueToObject(ctx, member, NULL);
 421     if (! JSObjectIsFunction(ctx, function))
 422         return JSC::Bindings::convertUndefinedToJObject();
 423     size_t argumentCount = env->GetArrayLength(args);
 424     JSValueRef *arguments = new JSValueRef[argumentCount];
 425     for (int i = 0;  i < argumentCount; i++) {
 426       JLObject jarg(env->GetObjectArrayElement(args, i));
 427         arguments[i] = WebCore::Java_Object_to_JSValue(env, ctx, rootObject.get(), jarg, accessControlContext);
 428     }
 429     JSValueRef exception = 0;
 430     JSValueRef result = JSObjectCallAsFunction(ctx, function, object,
 431                                                argumentCount,  arguments,
 432                                                &exception);
 433     delete[] arguments;
 434     if (exception) {
 435         WebCore::throwJavaException(env, ctx, exception, rootObject.get());
 436         return NULL;
 437     }
 438     return WebCore::JSValue_to_Java_Object(result, env, ctx, rootObject.get());
 439 }
 440 
 441 JNIEXPORT void JNICALL Java_com_sun_webkit_dom_JSObject_unprotectImpl
 442 (JNIEnv*, jclass, jlong peer, jint peer_type)
 443 {
 444     JSObjectRef object;
 445     JSContextRef ctx;
 446     RefPtr<JSC::Bindings::RootObject> rootObject(checkJSPeer(peer, peer_type, object, ctx));
 447     if (!rootObject || !peer || !ctx) {
 448         return;
 449     }
 450 
 451     rootObject->gcUnprotect(toJS(object));
 452 }
 453 
 454 }