Commit 9c4b2105 authored by mhahnenberg@apple.com's avatar mhahnenberg@apple.com

Objective-C API: Objective-C functions exposed to JavaScript have the wrong...

Objective-C API: Objective-C functions exposed to JavaScript have the wrong type (object instead of function)
https://bugs.webkit.org/show_bug.cgi?id=105892

Reviewed by Geoffrey Garen.

Changed ObjCCallbackFunction to subclass JSCallbackFunction which already has all of the machinery to call
functions using the C API. Since ObjCCallbackFunction is now a JSCell, we changed the old implementation of
ObjCCallbackFunction to be the internal implementation and keep track of all the proper data so that we 
don't have to put all of that in the header, which will now be included from C++ files (e.g. JSGlobalObject.cpp).

* API/JSCallbackFunction.cpp: Change JSCallbackFunction to allow subclassing. Originally it was internally
passing its own Structure up the chain of constructors, but we now want to be able to pass other Structures as well.
(JSC::JSCallbackFunction::JSCallbackFunction):
(JSC::JSCallbackFunction::create):
* API/JSCallbackFunction.h:
(JSCallbackFunction):
* API/JSWrapperMap.mm: Changed interface to tryUnwrapBlock.
(tryUnwrapObjcObject):
* API/ObjCCallbackFunction.h:
(ObjCCallbackFunction): Moved into the JSC namespace, just like JSCallbackFunction.
(JSC::ObjCCallbackFunction::createStructure): Overridden so that the correct ClassInfo gets used since we have 
a destructor.
(JSC::ObjCCallbackFunction::impl): Getter for the internal impl.
* API/ObjCCallbackFunction.mm:
(JSC::ObjCCallbackFunctionImpl::ObjCCallbackFunctionImpl): What used to be ObjCCallbackFunction is now 
ObjCCallbackFunctionImpl. It handles the Objective-C specific parts of managing callback functions.
(JSC::ObjCCallbackFunctionImpl::~ObjCCallbackFunctionImpl):
(JSC::objCCallbackFunctionCallAsFunction): Same as the old one, but now it casts to ObjCCallbackFunction and grabs the impl 
rather than using JSObjectGetPrivate.
(JSC::ObjCCallbackFunction::ObjCCallbackFunction): New bits to allow being part of the JSCell hierarchy.
(JSC::ObjCCallbackFunction::create):
(JSC::ObjCCallbackFunction::destroy):
(JSC::ObjCCallbackFunctionImpl::call): Handles the actual invocation, just like it used to.
(objCCallbackFunctionForInvocation):
(tryUnwrapBlock): Changed to check the ClassInfo for inheritance directly, rather than going through the C API call.
* API/tests/testapi.mm: Added new test to make sure that doing Function.prototype.toString.call(f) won't result in 
an error when f is an Objective-C method or block underneath the covers.
* runtime/JSGlobalObject.cpp: Added new Structure for ObjCCallbackFunction.
(JSC::JSGlobalObject::reset):
(JSC::JSGlobalObject::visitChildren):
* runtime/JSGlobalObject.h:
(JSGlobalObject):
(JSC::JSGlobalObject::objcCallbackFunctionStructure):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@145848 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 19687c9c
......@@ -44,8 +44,8 @@ ASSERT_HAS_TRIVIAL_DESTRUCTOR(JSCallbackFunction);
const ClassInfo JSCallbackFunction::s_info = { "CallbackFunction", &InternalFunction::s_info, 0, 0, CREATE_METHOD_TABLE(JSCallbackFunction) };
JSCallbackFunction::JSCallbackFunction(JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback callback)
: InternalFunction(globalObject, globalObject->callbackFunctionStructure())
JSCallbackFunction::JSCallbackFunction(JSGlobalObject* globalObject, Structure* structure, JSObjectCallAsFunctionCallback callback)
: InternalFunction(globalObject, structure)
, m_callback(callback)
{
}
......@@ -56,6 +56,13 @@ void JSCallbackFunction::finishCreation(JSGlobalData& globalData, const String&
ASSERT(inherits(&s_info));
}
JSCallbackFunction* JSCallbackFunction::create(ExecState* exec, JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback callback, const String& name)
{
JSCallbackFunction* function = new (NotNull, allocateCell<JSCallbackFunction>(*exec->heap())) JSCallbackFunction(globalObject, globalObject->callbackFunctionStructure(), callback);
function->finishCreation(exec->globalData(), name);
return function;
}
EncodedJSValue JSCallbackFunction::call(ExecState* exec)
{
JSContextRef execRef = toRef(exec);
......
......@@ -33,18 +33,13 @@ namespace JSC {
class JSCallbackFunction : public InternalFunction {
protected:
JSCallbackFunction(JSGlobalObject*, JSObjectCallAsFunctionCallback);
JSCallbackFunction(JSGlobalObject*, Structure*, JSObjectCallAsFunctionCallback);
void finishCreation(JSGlobalData&, const String& name);
public:
typedef InternalFunction Base;
static JSCallbackFunction* create(ExecState* exec, JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback callback, const String& name)
{
JSCallbackFunction* function = new (NotNull, allocateCell<JSCallbackFunction>(*exec->heap())) JSCallbackFunction(globalObject, callback);
function->finishCreation(exec->globalData(), name);
return function;
}
static JSCallbackFunction* create(ExecState*, JSGlobalObject*, JSObjectCallAsFunctionCallback, const String& name);
static const ClassInfo s_info;
......@@ -55,9 +50,10 @@ public:
return Structure::create(globalData, globalObject, proto, TypeInfo(ObjectType, StructureFlags), &s_info);
}
private:
protected:
static CallType getCallData(JSCell*, CallData&);
private:
static EncodedJSValue JSC_HOST_CALL call(ExecState*);
JSObjectCallAsFunctionCallback m_callback;
......
......@@ -530,7 +530,7 @@ id tryUnwrapObjcObject(JSGlobalContextRef context, JSValueRef value)
ASSERT(!exception);
if (JSValueIsObjectOfClass(context, object, wrapperClass()))
return (id)JSC::jsCast<JSC::JSAPIWrapperObject*>(toJS(object))->wrappedObject();
if (id target = tryUnwrapBlock(context, object))
if (id target = tryUnwrapBlock(object))
return target;
return nil;
}
......
......@@ -22,14 +22,52 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ObjCCallbackFunction_h
#define ObjCCallbackFunction_h
#import <JavaScriptCore/JavaScriptCore.h>
#include <JavaScriptCore/JSBase.h>
#if JSC_OBJC_API_ENABLED
#import <JavaScriptCore/JSCallbackFunction.h>
#if defined(__OBJC__)
JSObjectRef objCCallbackFunctionForMethod(JSContext *, Class, Protocol *, BOOL isInstanceMethod, SEL, const char* types);
JSObjectRef objCCallbackFunctionForBlock(JSContext *, id);
id tryUnwrapBlock(JSGlobalContextRef, JSObjectRef);
id tryUnwrapBlock(JSObjectRef);
#endif
namespace JSC {
class ObjCCallbackFunctionImpl;
class ObjCCallbackFunction : public JSCallbackFunction {
public:
typedef JSCallbackFunction Base;
static ObjCCallbackFunction* create(ExecState*, JSGlobalObject*, const String& name, PassOwnPtr<ObjCCallbackFunctionImpl>);
static void destroy(JSCell*);
static Structure* createStructure(JSGlobalData& globalData, JSGlobalObject* globalObject, JSValue prototype)
{
ASSERT(globalObject);
return Structure::create(globalData, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), &s_info);
}
static JS_EXPORTDATA const ClassInfo s_info;
ObjCCallbackFunctionImpl* impl() { return m_impl.get(); }
protected:
ObjCCallbackFunction(JSGlobalObject*, JSObjectCallAsFunctionCallback, PassOwnPtr<ObjCCallbackFunctionImpl>);
private:
OwnPtr<ObjCCallbackFunctionImpl> m_impl;
};
} // namespace JSC
#endif
#endif // ObjCCallbackFunction_h
......@@ -389,9 +389,11 @@ enum CallbackType {
CallbackBlock
};
class ObjCCallbackFunction {
namespace JSC {
class ObjCCallbackFunctionImpl {
public:
ObjCCallbackFunction(JSContext *context, NSInvocation *invocation, CallbackType type, Class instanceClass, PassOwnPtr<CallbackArgument> arguments, PassOwnPtr<CallbackResult> result)
ObjCCallbackFunctionImpl(JSContext *context, NSInvocation *invocation, CallbackType type, Class instanceClass, PassOwnPtr<CallbackArgument> arguments, PassOwnPtr<CallbackResult> result)
: m_context(context)
, m_type(type)
, m_instanceClass([instanceClass retain])
......@@ -402,7 +404,7 @@ public:
ASSERT(type != CallbackInstanceMethod || instanceClass);
}
~ObjCCallbackFunction()
~ObjCCallbackFunctionImpl()
{
if (m_type != CallbackInstanceMethod)
[[m_invocation.get() target] release];
......@@ -436,11 +438,6 @@ private:
OwnPtr<CallbackResult> m_result;
};
static void objCCallbackFunctionFinalize(JSObjectRef object)
{
delete static_cast<ObjCCallbackFunction*>(JSObjectGetPrivate(object));
}
static JSValueRef objCCallbackFunctionCallAsFunction(JSContextRef callerContext, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
// Retake the API lock - we need this for a few reasons:
......@@ -449,18 +446,19 @@ static JSValueRef objCCallbackFunctionCallAsFunction(JSContextRef callerContext,
// (3) We need to be locked (per context would be fine) against conflicting usage of the ObjCCallbackFunction's NSInvocation.
JSC::APIEntryShim entryShim(toJS(callerContext));
ObjCCallbackFunction* callback = static_cast<ObjCCallbackFunction*>(JSObjectGetPrivate(function));
JSContext *context = callback->context();
ObjCCallbackFunction* callback = static_cast<ObjCCallbackFunction*>(toJS(function));
ObjCCallbackFunctionImpl* impl = callback->impl();
JSContext *context = impl->context();
if (!context) {
context = [JSContext contextWithGlobalContextRef:toGlobalRef(toJS(callerContext)->lexicalGlobalObject()->globalExec())];
callback->setContext(context);
impl->setContext(context);
}
CallbackData callbackData;
JSValueRef result;
@autoreleasepool {
[context beginCallbackWithData:&callbackData thisValue:thisObject argumentCount:argumentCount arguments:arguments];
result = callback->call(context, thisObject, argumentCount, arguments, exception);
result = impl->call(context, thisObject, argumentCount, arguments, exception);
if (context.exception)
*exception = valueInternalValue(context.exception);
[context endCallbackWithData:&callbackData];
......@@ -468,26 +466,27 @@ static JSValueRef objCCallbackFunctionCallAsFunction(JSContextRef callerContext,
return result;
}
static JSClassRef createObjCCallbackFunctionClass()
const JSC::ClassInfo ObjCCallbackFunction::s_info = { "CallbackFunction", &Base::s_info, 0, 0, CREATE_METHOD_TABLE(ObjCCallbackFunction) };
ObjCCallbackFunction::ObjCCallbackFunction(JSC::JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback callback, PassOwnPtr<ObjCCallbackFunctionImpl> impl)
: Base(globalObject, globalObject->objcCallbackFunctionStructure(), callback)
, m_impl(impl)
{
JSClassDefinition definition;
definition = kJSClassDefinitionEmpty;
definition.className = "Function";
definition.finalize = objCCallbackFunctionFinalize;
definition.callAsFunction = objCCallbackFunctionCallAsFunction;
return JSClassCreate(&definition);
}
static JSClassRef objCCallbackFunctionClass()
ObjCCallbackFunction* ObjCCallbackFunction::create(JSC::ExecState* exec, JSC::JSGlobalObject* globalObject, const String& name, PassOwnPtr<ObjCCallbackFunctionImpl> impl)
{
static SpinLock initLock = SPINLOCK_INITIALIZER;
SpinLockHolder lockHolder(&initLock);
ObjCCallbackFunction* function = new (NotNull, allocateCell<ObjCCallbackFunction>(*exec->heap())) ObjCCallbackFunction(globalObject, objCCallbackFunctionCallAsFunction, impl);
function->finishCreation(exec->globalData(), name);
return function;
}
static JSClassRef classRef = createObjCCallbackFunctionClass();
return classRef;
void ObjCCallbackFunction::destroy(JSCell* cell)
{
static_cast<ObjCCallbackFunction*>(cell)->ObjCCallbackFunction::~ObjCCallbackFunction();
}
JSValueRef ObjCCallbackFunction::call(JSContext *context, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
JSValueRef ObjCCallbackFunctionImpl::call(JSContext *context, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
JSGlobalContextRef contextRef = [context globalContextRef];
......@@ -523,6 +522,8 @@ JSValueRef ObjCCallbackFunction::call(JSContext *context, JSObjectRef thisObject
return m_result->get(m_invocation.get(), context, exception);
}
} // namespace JSC
static bool blockSignatureContainsClass()
{
static bool containsClass = ^{
......@@ -578,20 +579,11 @@ static JSObjectRef objCCallbackFunctionForInvocation(JSContext *context, NSInvoc
++argumentCount;
}
// FIXME: https://bugs.webkit.org/show_bug.cgi?id=105892
// The result should be a regular host function, rather than a callable C API object.
// Patch in the right length & [Prototype] values for now, but should fix this.
// Function.prototype.toString currently fails, since this is not yet a subclass of functon, but call & apply do work.
JSObjectRef functionObject = JSObjectMake([context globalContextRef], objCCallbackFunctionClass(), new ObjCCallbackFunction(context, invocation, type, instanceClass, arguments.release(), result.release()));
JSValue *value = [JSValue valueWithValue:functionObject inContext:context];
value[@"length"] = @(argumentCount);
JSObjectSetPrototype([context globalContextRef], functionObject, valueInternalValue(context[@"Function"][@"prototype"]));
value[@"toString"] = [context evaluateScript:@"(function(){ return '"
"function <Objective-C>() {" "\\n"
" [native code]" "\\n"
"}" "\\n"
"'})"];
return functionObject;
JSC::ExecState* exec = toJS([context globalContextRef]);
JSC::APIEntryShim shim(exec);
OwnPtr<JSC::ObjCCallbackFunctionImpl> impl = adoptPtr(new JSC::ObjCCallbackFunctionImpl(context, invocation, type, instanceClass, arguments.release(), result.release()));
// FIXME: Maybe we could support having the selector as the name of the function to make it a bit more user-friendly from the JS side?
return toRef(JSC::ObjCCallbackFunction::create(exec, exec->lexicalGlobalObject(), "", impl.release()));
}
JSObjectRef objCCallbackFunctionForMethod(JSContext *context, Class cls, Protocol *protocol, BOOL isInstanceMethod, SEL sel, const char* types)
......@@ -613,11 +605,11 @@ JSObjectRef objCCallbackFunctionForBlock(JSContext *context, id target)
return objCCallbackFunctionForInvocation(context, invocation, CallbackBlock, nil, signature);
}
id tryUnwrapBlock(JSGlobalContextRef context, JSObjectRef object)
id tryUnwrapBlock(JSObjectRef object)
{
if (!JSValueIsObjectOfClass(context, object, objCCallbackFunctionClass()))
if (!toJS(object)->inherits(&JSC::ObjCCallbackFunction::s_info))
return nil;
return (static_cast<ObjCCallbackFunction*>(JSObjectGetPrivate(object)))->wrappedBlock();
return static_cast<JSC::ObjCCallbackFunction*>(toJS(object))->impl()->wrappedBlock();
}
#endif
......@@ -518,6 +518,15 @@ void testObjectiveCAPI()
checkResult(@"testObject.bogusCallback == undefined", [result isUndefined]);
}
@autoreleasepool {
JSContext *context = [[JSContext alloc] init];
TestObject *testObject = [TestObject testObject];
context[@"testObject"] = testObject;
JSValue *result = [context evaluateScript:@"Function.prototype.toString.call(testObject.callback)"];
NSLog(@"toString = %@", [result toString]);
checkResult(@"Function.prototype.toString", !context.exception && ![result isUndefined]);
}
@autoreleasepool {
JSContext *context1 = [[JSContext alloc] init];
JSContext *context2 = [[JSContext alloc] initWithVirtualMachine:context1.virtualMachine];
......
2013-03-11 Mark Hahnenberg <mhahnenberg@apple.com>
Objective-C API: Objective-C functions exposed to JavaScript have the wrong type (object instead of function)
https://bugs.webkit.org/show_bug.cgi?id=105892
Reviewed by Geoffrey Garen.
Changed ObjCCallbackFunction to subclass JSCallbackFunction which already has all of the machinery to call
functions using the C API. Since ObjCCallbackFunction is now a JSCell, we changed the old implementation of
ObjCCallbackFunction to be the internal implementation and keep track of all the proper data so that we
don't have to put all of that in the header, which will now be included from C++ files (e.g. JSGlobalObject.cpp).
* API/JSCallbackFunction.cpp: Change JSCallbackFunction to allow subclassing. Originally it was internally
passing its own Structure up the chain of constructors, but we now want to be able to pass other Structures as well.
(JSC::JSCallbackFunction::JSCallbackFunction):
(JSC::JSCallbackFunction::create):
* API/JSCallbackFunction.h:
(JSCallbackFunction):
* API/JSWrapperMap.mm: Changed interface to tryUnwrapBlock.
(tryUnwrapObjcObject):
* API/ObjCCallbackFunction.h:
(ObjCCallbackFunction): Moved into the JSC namespace, just like JSCallbackFunction.
(JSC::ObjCCallbackFunction::createStructure): Overridden so that the correct ClassInfo gets used since we have
a destructor.
(JSC::ObjCCallbackFunction::impl): Getter for the internal impl.
* API/ObjCCallbackFunction.mm:
(JSC::ObjCCallbackFunctionImpl::ObjCCallbackFunctionImpl): What used to be ObjCCallbackFunction is now
ObjCCallbackFunctionImpl. It handles the Objective-C specific parts of managing callback functions.
(JSC::ObjCCallbackFunctionImpl::~ObjCCallbackFunctionImpl):
(JSC::objCCallbackFunctionCallAsFunction): Same as the old one, but now it casts to ObjCCallbackFunction and grabs the impl
rather than using JSObjectGetPrivate.
(JSC::ObjCCallbackFunction::ObjCCallbackFunction): New bits to allow being part of the JSCell hierarchy.
(JSC::ObjCCallbackFunction::create):
(JSC::ObjCCallbackFunction::destroy):
(JSC::ObjCCallbackFunctionImpl::call): Handles the actual invocation, just like it used to.
(objCCallbackFunctionForInvocation):
(tryUnwrapBlock): Changed to check the ClassInfo for inheritance directly, rather than going through the C API call.
* API/tests/testapi.mm: Added new test to make sure that doing Function.prototype.toString.call(f) won't result in
an error when f is an Objective-C method or block underneath the covers.
* runtime/JSGlobalObject.cpp: Added new Structure for ObjCCallbackFunction.
(JSC::JSGlobalObject::reset):
(JSC::JSGlobalObject::visitChildren):
* runtime/JSGlobalObject.h:
(JSGlobalObject):
(JSC::JSGlobalObject::objcCallbackFunctionStructure):
2013-03-14 Mark Hahnenberg <mhahnenberg@apple.com>
Objective-C API: Nested dictionaries are not converted properly in the Objective-C binding
......
......@@ -58,6 +58,7 @@ javascriptcore_sources += \
Source/JavaScriptCore/API/JSStringRef.cpp \
Source/JavaScriptCore/API/JSValueRef.cpp \
Source/JavaScriptCore/API/JSWeakObjectMapRefInternal.h \
Source/JavaScriptCore/API/ObjCCallbackFunction.h \
Source/JavaScriptCore/API/OpaqueJSString.cpp \
Source/JavaScriptCore/API/OpaqueJSString.h \
Source/JavaScriptCore/assembler/AbstractMacroAssembler.h \
......
......@@ -1549,6 +1549,10 @@
RelativePath="..\..\API\JSWeakObjectMapRefPrivate.h"
>
</File>
<File
RelativePath="..\..\API\ObjCCallbackFunction.h"
>
</File>
<File
RelativePath="..\..\API\OpaqueJSString.cpp"
>
......
......@@ -69,6 +69,7 @@
#include "NativeErrorPrototype.h"
#include "NumberConstructor.h"
#include "NumberPrototype.h"
#include "ObjCCallbackFunction.h"
#include "ObjectConstructor.h"
#include "ObjectPrototype.h"
#include "Operations.h"
......@@ -230,6 +231,9 @@ void JSGlobalObject::reset(JSValue prototype)
m_argumentsStructure.set(exec->globalData(), this, Arguments::createStructure(exec->globalData(), this, m_objectPrototype.get()));
m_callbackConstructorStructure.set(exec->globalData(), this, JSCallbackConstructor::createStructure(exec->globalData(), this, m_objectPrototype.get()));
m_callbackObjectStructure.set(exec->globalData(), this, JSCallbackObject<JSDestructibleObject>::createStructure(exec->globalData(), this, m_objectPrototype.get()));
#if JSC_OBJC_API_ENABLED
m_objcCallbackFunctionStructure.set(exec->globalData(), this, ObjCCallbackFunction::createStructure(exec->globalData(), this, m_functionPrototype.get()));
#endif
m_objcWrapperObjectStructure.set(exec->globalData(), this, JSCallbackObject<JSAPIWrapperObject>::createStructure(exec->globalData(), this, m_objectPrototype.get()));
m_arrayPrototype.set(exec->globalData(), this, ArrayPrototype::create(exec, this, ArrayPrototype::createStructure(exec->globalData(), this, m_objectPrototype.get())));
......@@ -513,6 +517,9 @@ void JSGlobalObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
visitor.append(&thisObject->m_callbackConstructorStructure);
visitor.append(&thisObject->m_callbackFunctionStructure);
visitor.append(&thisObject->m_callbackObjectStructure);
#if JSC_OBJC_API_ENABLED
visitor.append(&thisObject->m_objcCallbackFunctionStructure);
#endif
visitor.append(&thisObject->m_objcWrapperObjectStructure);
visitor.append(&thisObject->m_dateStructure);
visitor.append(&thisObject->m_nullPrototypeObjectStructure);
......
......@@ -33,6 +33,7 @@
#include "StructureChain.h"
#include "StructureRareDataInlines.h"
#include "Watchpoint.h"
#include <JavaScriptCore/JSBase.h>
#include <wtf/HashSet.h>
#include <wtf/OwnPtr.h>
#include <wtf/RandomNumber.h>
......@@ -141,6 +142,9 @@ protected:
WriteBarrier<Structure> m_callbackConstructorStructure;
WriteBarrier<Structure> m_callbackFunctionStructure;
WriteBarrier<Structure> m_callbackObjectStructure;
#if JSC_OBJC_API_ENABLED
WriteBarrier<Structure> m_objcCallbackFunctionStructure;
#endif
WriteBarrier<Structure> m_objcWrapperObjectStructure;
WriteBarrier<Structure> m_dateStructure;
WriteBarrier<Structure> m_nullPrototypeObjectStructure;
......@@ -301,6 +305,9 @@ public:
Structure* callbackConstructorStructure() const { return m_callbackConstructorStructure.get(); }
Structure* callbackFunctionStructure() const { return m_callbackFunctionStructure.get(); }
Structure* callbackObjectStructure() const { return m_callbackObjectStructure.get(); }
#if JSC_OBJC_API_ENABLED
Structure* objcCallbackFunctionStructure() const { return m_objcCallbackFunctionStructure.get(); }
#endif
Structure* objcWrapperObjectStructure() const { return m_objcWrapperObjectStructure.get(); }
Structure* dateStructure() const { return m_dateStructure.get(); }
Structure* nullPrototypeObjectStructure() const { return m_nullPrototypeObjectStructure.get(); }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment