mirror of
https://github.com/status-im/react-native.git
synced 2025-03-01 01:20:31 +00:00
Move Fabric JSC bindings to oss
Reviewed By: mdvacca Differential Revision: D7205065 fbshipit-source-id: 5876cb3e08ee96e39b80e6b377c60600f404ca21
This commit is contained in:
parent
bec97dc243
commit
3a2bdf5c50
@ -0,0 +1,24 @@
|
||||
load("@xplat//ReactNative:DEFS.bzl", "react_native_dep", "react_native_target", "rn_android_library", "IS_OSS_BUILD")
|
||||
|
||||
rn_android_library(
|
||||
name = "jsc",
|
||||
srcs = glob(["**/*.java"]),
|
||||
exported_deps = [
|
||||
react_native_dep("java/com/facebook/jni:jni"),
|
||||
react_native_dep("java/com/facebook/proguard/annotations:annotations"),
|
||||
],
|
||||
provided_deps = [
|
||||
react_native_dep("third-party/android/support/v4:lib-support-v4"),
|
||||
],
|
||||
required_for_source_only_abi = True,
|
||||
visibility = [
|
||||
"PUBLIC",
|
||||
],
|
||||
deps = [
|
||||
react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"),
|
||||
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
|
||||
react_native_target("java/com/facebook/react/bridge:bridge"),
|
||||
react_native_target("java/com/facebook/react/fabric:fabric"),
|
||||
react_native_target("java/com/facebook/react/fabric/jsc/jni:jni"),
|
||||
],
|
||||
)
|
@ -0,0 +1,35 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.react.fabric.jsc;
|
||||
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.bridge.JavaScriptContextHolder;
|
||||
import com.facebook.react.fabric.FabricBinding;
|
||||
import com.facebook.react.fabric.FabricUIManager;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
@DoNotStrip
|
||||
public class FabricJSCBinding implements FabricBinding {
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary("fabricjscjni");
|
||||
}
|
||||
|
||||
// used from native
|
||||
@SuppressWarnings("unused")
|
||||
private final HybridData mHybridData;
|
||||
|
||||
private static native HybridData initHybrid();
|
||||
|
||||
private native void installFabric(long jsContextNativePointer, Object fabricModule);
|
||||
|
||||
public FabricJSCBinding() {
|
||||
mHybridData = initHybrid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installFabric(JavaScriptContextHolder jsContext, FabricUIManager fabricModule) {
|
||||
installFabric(jsContext.get(), fabricModule);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
load("@xplat//ReactNative:DEFS.bzl", "react_native_target", "react_native_xplat_target", "rn_xplat_cxx_library", "FBJNI_TARGET", "ANDROID")
|
||||
|
||||
rn_xplat_cxx_library(
|
||||
name = "jni",
|
||||
srcs = glob(["*.cpp"]),
|
||||
headers = glob(["*.h"]),
|
||||
compiler_flags = [
|
||||
"-Wall",
|
||||
"-fexceptions",
|
||||
"-std=gnu++1y",
|
||||
],
|
||||
platforms = ANDROID,
|
||||
soname = "libfabricjscjni.$(ext)",
|
||||
visibility = ["PUBLIC"],
|
||||
deps = [
|
||||
FBJNI_TARGET,
|
||||
react_native_xplat_target("jschelpers:jschelpers"),
|
||||
react_native_target("jni/react/jni:jni"),
|
||||
],
|
||||
)
|
@ -0,0 +1,296 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "FabricJSCBinding.h"
|
||||
#include <fb/fbjni.h>
|
||||
#include <react/jni/ReadableNativeMap.h>
|
||||
#include <jschelpers/JavaScriptCore.h>
|
||||
#include <jschelpers/Unicode.h>
|
||||
|
||||
using namespace facebook::jni;
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
namespace {
|
||||
|
||||
bool useCustomJSC = false;
|
||||
|
||||
struct JList : public JavaClass<JList> {
|
||||
static constexpr auto kJavaDescriptor = "Ljava/util/List;";
|
||||
};
|
||||
|
||||
struct JShadowNode : public JavaClass<JShadowNode> {
|
||||
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/uimanager/ReactShadowNode;";
|
||||
};
|
||||
|
||||
typedef struct FabricJSCUIManager {
|
||||
FabricJSCUIManager(alias_ref<jobject> module, JSClassRef classRef, bool customJSC)
|
||||
: wrapperObjectClassRef(classRef)
|
||||
, useCustomJSC(customJSC) {
|
||||
fabricUiManager = make_global(module);
|
||||
}
|
||||
global_ref<jobject> fabricUiManager;
|
||||
JSClassRef wrapperObjectClassRef;
|
||||
bool useCustomJSC;
|
||||
} FabricJSCUIManager;
|
||||
|
||||
jobject makePlainGlobalRef(jobject object) {
|
||||
// When storing the global reference we need it to be a plain
|
||||
// pointer. That's why we use plain jni instead of fbjni here.
|
||||
return Environment::current()->NewGlobalRef(object);
|
||||
}
|
||||
|
||||
local_ref<JString> JSValueToJString(JSContextRef ctx, JSValueRef value) {
|
||||
JSStringRef strRef = JSC_JSValueToStringCopy(ctx, value, NULL);
|
||||
const size_t size = JSStringGetMaximumUTF8CStringSize(strRef);
|
||||
char buffer[size];
|
||||
JSStringGetUTF8CString(strRef, buffer, size);
|
||||
JSC_JSStringRelease(ctx, strRef);
|
||||
return make_jstring(buffer);
|
||||
}
|
||||
|
||||
local_ref<JShadowNode> JSValueToJShadowNode(JSContextRef ctx, JSValueRef value) {
|
||||
JSObjectRef obj = JSC_JSValueToObject(ctx, value, NULL);
|
||||
auto node = static_cast<JShadowNode::javaobject>(JSC_JSObjectGetPrivate(useCustomJSC, obj));
|
||||
return make_local(node);
|
||||
}
|
||||
|
||||
local_ref<JList> JSValueToJList(JSContextRef ctx, JSValueRef value) {
|
||||
JSObjectRef obj = JSC_JSValueToObject(ctx, value, NULL);
|
||||
auto node = static_cast<JList::javaobject>(JSC_JSObjectGetPrivate(useCustomJSC, obj));
|
||||
return make_local(node);
|
||||
}
|
||||
|
||||
local_ref<ReadableNativeMap::jhybridobject> JSValueToReadableMapViaJSON(JSContextRef ctx, JSValueRef value) {
|
||||
JSStringRef jsonRef = JSC_JSValueCreateJSONString(ctx, value, 0, NULL);
|
||||
size_t size = JSC_JSStringGetLength(ctx, jsonRef);
|
||||
const JSChar* utf16 = JSC_JSStringGetCharactersPtr(ctx, jsonRef);
|
||||
std::string json = unicode::utf16toUTF8(utf16, size);
|
||||
JSC_JSStringRelease(ctx, jsonRef);
|
||||
folly::dynamic dynamicValue = folly::parseJson(json);
|
||||
return ReadableNativeMap::newObjectCxxArgs(std::move(dynamicValue));
|
||||
}
|
||||
|
||||
JSValueRef createNode(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) {
|
||||
FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function);
|
||||
alias_ref<jobject> manager = managerWrapper->fabricUiManager;
|
||||
JSClassRef classRef = managerWrapper->wrapperObjectClassRef;
|
||||
|
||||
static auto createNode =
|
||||
jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
|
||||
->getMethod<alias_ref<JShadowNode>(jint, jstring, jint, ReadableNativeMap::javaobject)>("createNode");
|
||||
|
||||
int reactTag = (int)JSC_JSValueToNumber(ctx, arguments[0], NULL);
|
||||
auto viewName = JSValueToJString(ctx, arguments[1]);
|
||||
int rootTag = (int)JSC_JSValueToNumber(ctx, arguments[2], NULL);
|
||||
auto props = JSC_JSValueIsNull(ctx, arguments[3]) ? local_ref<ReadableNativeMap::jhybridobject>(nullptr) :
|
||||
JSValueToReadableMapViaJSON(ctx, arguments[3]);;
|
||||
|
||||
// TODO: Retain object in arguments[4] using a weak ref.
|
||||
|
||||
auto node = createNode(manager, reactTag, viewName.get(), rootTag, props.get());
|
||||
|
||||
return JSC_JSObjectMake(ctx, classRef, makePlainGlobalRef(node.get()));
|
||||
}
|
||||
|
||||
JSValueRef cloneNode(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) {
|
||||
FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function);
|
||||
alias_ref<jobject> manager = managerWrapper->fabricUiManager;
|
||||
JSClassRef classRef = managerWrapper->wrapperObjectClassRef;
|
||||
|
||||
static auto cloneNode =
|
||||
jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
|
||||
->getMethod<alias_ref<JShadowNode>(JShadowNode::javaobject)>("cloneNode");
|
||||
|
||||
auto previousNode = JSValueToJShadowNode(ctx, arguments[0]);
|
||||
auto newNode = cloneNode(manager, previousNode.get());
|
||||
|
||||
return JSC_JSObjectMake(ctx, classRef, makePlainGlobalRef(newNode.get()));
|
||||
}
|
||||
|
||||
JSValueRef cloneNodeWithNewChildren(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) {
|
||||
FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function);
|
||||
alias_ref<jobject> manager = managerWrapper->fabricUiManager;
|
||||
JSClassRef classRef = managerWrapper->wrapperObjectClassRef;
|
||||
|
||||
static auto cloneNodeWithNewChildren =
|
||||
jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
|
||||
->getMethod<alias_ref<JShadowNode>(JShadowNode::javaobject)>("cloneNodeWithNewChildren");
|
||||
|
||||
auto previousNode = JSValueToJShadowNode(ctx, arguments[0]);
|
||||
auto newNode = cloneNodeWithNewChildren(manager, previousNode.get());
|
||||
|
||||
return JSC_JSObjectMake(ctx, classRef, makePlainGlobalRef(newNode.get()));
|
||||
}
|
||||
|
||||
JSValueRef cloneNodeWithNewProps(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) {
|
||||
FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function);
|
||||
alias_ref<jobject> manager = managerWrapper->fabricUiManager;
|
||||
JSClassRef classRef = managerWrapper->wrapperObjectClassRef;
|
||||
|
||||
static auto cloneNodeWithNewProps =
|
||||
jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
|
||||
->getMethod<alias_ref<JShadowNode>(JShadowNode::javaobject, ReadableNativeMap::javaobject)>("cloneNodeWithNewProps");
|
||||
|
||||
auto previousNode = JSValueToJShadowNode(ctx, arguments[0]);
|
||||
auto props = JSValueToReadableMapViaJSON(ctx, arguments[1]);
|
||||
auto newNode = cloneNodeWithNewProps(manager, previousNode.get(), props.get());
|
||||
|
||||
return JSC_JSObjectMake(ctx, classRef, makePlainGlobalRef(newNode.get()));
|
||||
}
|
||||
|
||||
JSValueRef cloneNodeWithNewChildrenAndProps(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) {
|
||||
FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function);
|
||||
alias_ref<jobject> manager = managerWrapper->fabricUiManager;
|
||||
JSClassRef classRef = managerWrapper->wrapperObjectClassRef;
|
||||
|
||||
static auto cloneNodeWithNewChildrenAndProps =
|
||||
jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
|
||||
->getMethod<alias_ref<JShadowNode>(JShadowNode::javaobject, ReadableNativeMap::javaobject)>("cloneNodeWithNewChildrenAndProps");
|
||||
|
||||
auto previousNode = JSValueToJShadowNode(ctx, arguments[0]);
|
||||
auto props = JSValueToReadableMapViaJSON(ctx, arguments[1]);
|
||||
auto newNode = cloneNodeWithNewChildrenAndProps(manager, previousNode.get(), props.get());
|
||||
|
||||
return JSC_JSObjectMake(ctx, classRef, makePlainGlobalRef(newNode.get()));
|
||||
}
|
||||
|
||||
JSValueRef appendChild(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) {
|
||||
FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function);
|
||||
alias_ref<jobject> manager = managerWrapper->fabricUiManager;
|
||||
|
||||
static auto appendChild =
|
||||
jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
|
||||
->getMethod<void(JShadowNode::javaobject, JShadowNode::javaobject)>("appendChild");
|
||||
|
||||
auto parentNode = JSValueToJShadowNode(ctx, arguments[0]);
|
||||
auto childNode = JSValueToJShadowNode(ctx, arguments[1]);
|
||||
|
||||
appendChild(manager, parentNode.get(), childNode.get());
|
||||
|
||||
return JSC_JSValueMakeUndefined(ctx);
|
||||
}
|
||||
|
||||
JSValueRef createChildSet(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) {
|
||||
FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function);
|
||||
alias_ref<jobject> manager = managerWrapper->fabricUiManager;
|
||||
JSClassRef classRef = managerWrapper->wrapperObjectClassRef;
|
||||
|
||||
static auto createChildSet =
|
||||
jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
|
||||
->getMethod<alias_ref<JList>(jint)>("createChildSet");
|
||||
|
||||
int rootTag = (int)JSC_JSValueToNumber(ctx, arguments[0], NULL);
|
||||
auto childSet = createChildSet(manager, rootTag);
|
||||
|
||||
return JSC_JSObjectMake(ctx, classRef, makePlainGlobalRef(childSet.get()));
|
||||
}
|
||||
|
||||
JSValueRef appendChildToSet(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) {
|
||||
FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function);
|
||||
alias_ref<jobject> manager = managerWrapper->fabricUiManager;
|
||||
|
||||
static auto appendChildToSet =
|
||||
jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
|
||||
->getMethod<void(JList::javaobject, JShadowNode::javaobject)>("appendChildToSet");
|
||||
|
||||
auto childSet = JSValueToJList(ctx, arguments[0]);
|
||||
auto childNode = JSValueToJShadowNode(ctx, arguments[1]);
|
||||
|
||||
appendChildToSet(manager, childSet.get(), childNode.get());
|
||||
|
||||
return JSC_JSValueMakeUndefined(ctx);
|
||||
}
|
||||
|
||||
JSValueRef completeRoot(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) {
|
||||
FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function);
|
||||
alias_ref<jobject> manager = managerWrapper->fabricUiManager;
|
||||
|
||||
static auto completeRoot =
|
||||
jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
|
||||
->getMethod<void(jint, JList::javaobject)>("completeRoot");
|
||||
|
||||
int rootTag = (int)JSC_JSValueToNumber(ctx, arguments[0], NULL);
|
||||
auto childSet = JSValueToJList(ctx, arguments[1]);
|
||||
|
||||
completeRoot(manager, rootTag, childSet.get());
|
||||
|
||||
return JSC_JSValueMakeUndefined(ctx);
|
||||
}
|
||||
|
||||
void finalizeJNIObject(JSObjectRef object) {
|
||||
// Release whatever global ref object we're storing here.
|
||||
jobject globalRef = (jobject)JSC_JSObjectGetPrivate(useCustomJSC, object);
|
||||
Environment::current()->DeleteGlobalRef(globalRef);
|
||||
}
|
||||
|
||||
void finalizeWrapper(JSObjectRef object) {
|
||||
FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, object);
|
||||
delete managerWrapper;
|
||||
}
|
||||
|
||||
void addFabricMethod(
|
||||
JSContextRef context,
|
||||
jni::alias_ref<jobject> fabricModule,
|
||||
JSClassRef nodeClassRef,
|
||||
JSObjectRef module,
|
||||
const char *name,
|
||||
JSObjectCallAsFunctionCallback callback
|
||||
) {
|
||||
JSClassDefinition definition = kJSClassDefinitionEmpty;
|
||||
definition.callAsFunction = callback;
|
||||
definition.finalize = finalizeWrapper;
|
||||
JSClassRef classRef = JSC_JSClassCreate(useCustomJSC, &definition);
|
||||
FabricJSCUIManager *managerWrapper = new FabricJSCUIManager(fabricModule, nodeClassRef, useCustomJSC);
|
||||
JSObjectRef functionRef = JSC_JSObjectMake(context, classRef, managerWrapper);
|
||||
JSC_JSClassRelease(useCustomJSC, classRef);
|
||||
|
||||
JSStringRef nameStr = JSC_JSStringCreateWithUTF8CString(context, name);
|
||||
JSC_JSObjectSetProperty(context, module, nameStr, functionRef, kJSPropertyAttributeNone, NULL);
|
||||
JSC_JSStringRelease(context, nameStr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
jni::local_ref<FabricJSCBinding::jhybriddata> FabricJSCBinding::initHybrid(
|
||||
jni::alias_ref<jclass>) {
|
||||
return makeCxxInstance();
|
||||
}
|
||||
|
||||
void FabricJSCBinding::installFabric(jlong jsContextNativePointer,
|
||||
jni::alias_ref<jobject> fabricModule) {
|
||||
JSContextRef context = (JSContextRef)jsContextNativePointer;
|
||||
useCustomJSC = facebook::react::isCustomJSCPtr(context);
|
||||
|
||||
JSObjectRef module = JSC_JSObjectMake(context, NULL, NULL);
|
||||
|
||||
// Class definition for wrapper objects around nodes and sets
|
||||
JSClassDefinition definition = kJSClassDefinitionEmpty;
|
||||
definition.finalize = finalizeJNIObject;
|
||||
JSClassRef classRef = JSC_JSClassCreate(useCustomJSC, &definition);
|
||||
|
||||
addFabricMethod(context, fabricModule, classRef, module, "createNode", createNode);
|
||||
addFabricMethod(context, fabricModule, classRef, module, "cloneNode", cloneNode);
|
||||
addFabricMethod(context, fabricModule, classRef, module, "cloneNodeWithNewChildren", cloneNodeWithNewChildren);
|
||||
addFabricMethod(context, fabricModule, classRef, module, "cloneNodeWithNewProps", cloneNodeWithNewProps);
|
||||
addFabricMethod(context, fabricModule, classRef, module, "cloneNodeWithNewChildrenAndProps", cloneNodeWithNewChildrenAndProps);
|
||||
addFabricMethod(context, fabricModule, classRef, module, "appendChild", appendChild);
|
||||
addFabricMethod(context, fabricModule, classRef, module, "createChildSet", createChildSet);
|
||||
addFabricMethod(context, fabricModule, classRef, module, "appendChildToSet", appendChildToSet);
|
||||
addFabricMethod(context, fabricModule, classRef, module, "completeRoot", completeRoot);
|
||||
|
||||
JSObjectRef globalObject = JSC_JSContextGetGlobalObject(context);
|
||||
JSStringRef globalName = JSC_JSStringCreateWithUTF8CString(context, "nativeFabricUIManager");
|
||||
JSC_JSObjectSetProperty(context, globalObject, globalName, module, kJSPropertyAttributeNone, NULL);
|
||||
JSC_JSStringRelease(context, globalName);
|
||||
}
|
||||
|
||||
void FabricJSCBinding::registerNatives() {
|
||||
registerHybrid({
|
||||
makeNativeMethod("initHybrid", FabricJSCBinding::initHybrid),
|
||||
makeNativeMethod("installFabric", FabricJSCBinding::installFabric),
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <fb/fbjni.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class Instance;
|
||||
|
||||
class FabricJSCBinding : public jni::HybridClass<FabricJSCBinding> {
|
||||
public:
|
||||
constexpr static const char *const kJavaDescriptor =
|
||||
"Lcom/facebook/react/fabric/jsc/FabricJSCBinding;";
|
||||
|
||||
static void registerNatives();
|
||||
|
||||
private:
|
||||
|
||||
static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jclass>);
|
||||
|
||||
void installFabric(jlong jsContextNativePointer, jni::alias_ref<jobject> fabricModule);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include <fb/fbjni.h>
|
||||
#include <fb/xplat_init.h>
|
||||
|
||||
#include "FabricJSCBinding.h"
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
|
||||
return facebook::xplat::initialize(vm, [] {
|
||||
facebook::react::FabricJSCBinding::registerNatives();
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user