Commit d2d6baf8 authored by zandobersek@gmail.com's avatar zandobersek@gmail.com

JSC bindings generator should generate deletable JSC functions

https://bugs.webkit.org/show_bug.cgi?id=122422

Reviewed by Geoffrey Garen.

Source/WebCore: 

The JSC functions that the JSC bindings generator generates should be deletable to conform to
the WebIDL specification, which instructs that the WebIDL operations must be configurable (which
translates to the JSC functions being deletable).

The generator will still produce a non-deletable JSC function for operations under almost all
Web-facing interfaces since they're annotated with the OperationsNotDeletable attribute. The
exception here is the Node interface that is having the attribute removed, with the provided
test case testing that all the functions on the Node prototype object are writable, enumerable
and configurable. This behavior conforms to the WebIDL specification and the behaviors of IE
and Firefox. Chrome at the moment still provides non-configurable functions.

Test: fast/dom/webidl-operations-on-node-prototype.html

* bindings/scripts/CodeGeneratorJS.pm:
(GenerateImplementation): Enforce the non-deletable behavior of the JSC function if either the
operation's interface is annotated with the OperationsNotDeletable attribute or the operation itself
is annotated with the NotDeletable attribute.
* bindings/scripts/test/JS/JSTestActiveDOMObject.cpp: Update the JSC generator test baselines.
* bindings/scripts/test/JS/JSTestCustomNamedGetter.cpp: Ditto.
* bindings/scripts/test/JS/JSTestEventTarget.cpp: Ditto.
* bindings/scripts/test/JS/JSTestInterface.cpp: Ditto.
* bindings/scripts/test/JS/JSTestMediaQueryListListener.cpp: Ditto.
* bindings/scripts/test/JS/JSTestObj.cpp: Ditto.
* bindings/scripts/test/JS/JSTestTypedefs.cpp: Ditto.
* dom/Node.idl: Remove the OperationsNotDeletable attribute.

LayoutTests: 

Test that all the functions on the Node prototype object (apart from the constructor) are
writable, enumerable and configurable, as expected for WebIDL operations. This matches the
WebIDL specification as well as IE and Firefox.

Other affected test cases and baselines are updated to reflect the new behavior.

* fast/dom/webidl-operations-on-node-prototype-expected.txt: Added.
* fast/dom/webidl-operations-on-node-prototype.html: Added.
* js/dom/getOwnPropertyDescriptor-expected.txt:
* js/resources/getOwnPropertyDescriptor.js:
* platform/mac/canvas/philip/tests/type.prototype-expected.txt: Removed.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@159100 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 390a0c08
2013-11-12 Zan Dobersek <zdobersek@igalia.com>
JSC bindings generator should generate deletable JSC functions
https://bugs.webkit.org/show_bug.cgi?id=122422
Reviewed by Geoffrey Garen.
Test that all the functions on the Node prototype object (apart from the constructor) are
writable, enumerable and configurable, as expected for WebIDL operations. This matches the
WebIDL specification as well as IE and Firefox.
Other affected test cases and baselines are updated to reflect the new behavior.
* fast/dom/webidl-operations-on-node-prototype-expected.txt: Added.
* fast/dom/webidl-operations-on-node-prototype.html: Added.
* js/dom/getOwnPropertyDescriptor-expected.txt:
* js/resources/getOwnPropertyDescriptor.js:
* platform/mac/canvas/philip/tests/type.prototype-expected.txt: Removed.
2013-11-11 Simon Fraser <simon.fraser@apple.com>
REGRESSION (r155660): box-shadow causes overlay scrollbars to be in the wrong position when element is composited (85647)
......
Operations on the Node prototype object should be writable, enumerable and configurable.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS successfullyParsed is true
TEST COMPLETE
<html>
<head>
<script src="../../resources/js-test-pre.js"></script>
<script>
description("Operations on the Node prototype object should be writable, enumerable and configurable.");
Object.getOwnPropertyNames(Node.prototype).forEach(function(property) {
var propertyDescriptor = Object.getOwnPropertyDescriptor(Node.prototype, property);
if (typeof propertyDescriptor.value !== "function" || property === "constructor")
return;
[
{
name: "writable",
test: function(property) {
var propertyValue = Node.prototype[property];
Node.prototype[property] = 42;
if (Node.prototype[property] !== 42)
testFailed("Property " + property + " should be writable.");
Node.prototype[property] = propertyValue;
}
},
{
name: "enumerable",
test: function(property) {
if (Object.keys(Node.prototype).indexOf(property) < 0)
testFailed("Property " + property + " should be enumerable.");
}
},
{
name: "configurable",
test: function() {
var propertyValue = Node.prototype[property];
delete Node.prototype[property];
if (Node.prototype[property] !== undefined)
testFailed("Property " + property + " should be configurable.");
Node.prototype[property] = propertyValue;
}
}
].forEach(function(attribute) {
var propertyAttribute = "propertyDescriptor." + attribute.name;
if (eval(propertyAttribute) !== true)
testFailed(propertyAttribute + " for property " + property + " should be true.");
attribute.test(property);
});
});
</script>
<script src="../../resources/js-test-post.js"></script>
</head>
<body>
<div id="console"></div>
</body>
</html>
......@@ -33,7 +33,7 @@ PASS Object.getOwnPropertyDescriptor(document.__proto__.__proto__, 'createElemen
PASS Object.getOwnPropertyDescriptor(document.__proto__.__proto__, 'createElement').hasOwnProperty('get') is false
PASS Object.getOwnPropertyDescriptor(document.__proto__.__proto__, 'createElement').hasOwnProperty('set') is false
PASS Object.getOwnPropertyDescriptor(document.__proto__.__proto__, 'createElement').enumerable is true
PASS Object.getOwnPropertyDescriptor(document.__proto__.__proto__, 'createElement').configurable is false
PASS Object.getOwnPropertyDescriptor(document.__proto__.__proto__, 'createElement').configurable is true
PASS Object.getOwnPropertyDescriptor(Number, 'NEGATIVE_INFINITY').value is Number.NEGATIVE_INFINITY
PASS Object.getOwnPropertyDescriptor(Number, 'NEGATIVE_INFINITY').hasOwnProperty('get') is false
PASS Object.getOwnPropertyDescriptor(Number, 'NEGATIVE_INFINITY').hasOwnProperty('set') is false
......
......@@ -23,7 +23,7 @@ descriptorShouldBe("Array.prototype", "'concat'", {writable: true, enumerable: f
descriptorShouldBe("Date.prototype", "'toISOString'", {writable: true, enumerable: false, configurable: true, value: "Date.prototype.toISOString"});
descriptorShouldBe("String.prototype", "'concat'", {writable: true, enumerable: false, configurable: true, value:"String.prototype.concat"});
descriptorShouldBe("RegExp.prototype", "'exec'", {writable: true, enumerable: false, configurable: true, value:"RegExp.prototype.exec"});
descriptorShouldBe("document.__proto__.__proto__", "'createElement'", {writable: true, enumerable: true, configurable: false, value:"document.createElement"});
descriptorShouldBe("document.__proto__.__proto__", "'createElement'", {writable: true, enumerable: true, configurable: true, value:"document.createElement"});
descriptorShouldBe("Number", "'NEGATIVE_INFINITY'", {writable: false, enumerable: false, configurable: false, value:"Number.NEGATIVE_INFINITY"});
descriptorShouldBe("RegExp", "'$_'", {writable: true, enumerable: false, configurable: true, value:"RegExp.$_"});
descriptorShouldBe("/a/g", "'global'", {writable: true, enumerable: false, configurable: false, value:true});
......
Failed assertion window.HTMLCanvasElement.prototype.getContext === undefined (got 1[number], expected [undefined])
2013-11-12 Zan Dobersek <zdobersek@igalia.com>
JSC bindings generator should generate deletable JSC functions
https://bugs.webkit.org/show_bug.cgi?id=122422
Reviewed by Geoffrey Garen.
The JSC functions that the JSC bindings generator generates should be deletable to conform to
the WebIDL specification, which instructs that the WebIDL operations must be configurable (which
translates to the JSC functions being deletable).
The generator will still produce a non-deletable JSC function for operations under almost all
Web-facing interfaces since they're annotated with the OperationsNotDeletable attribute. The
exception here is the Node interface that is having the attribute removed, with the provided
test case testing that all the functions on the Node prototype object are writable, enumerable
and configurable. This behavior conforms to the WebIDL specification and the behaviors of IE
and Firefox. Chrome at the moment still provides non-configurable functions.
Test: fast/dom/webidl-operations-on-node-prototype.html
* bindings/scripts/CodeGeneratorJS.pm:
(GenerateImplementation): Enforce the non-deletable behavior of the JSC function if either the
operation's interface is annotated with the OperationsNotDeletable attribute or the operation itself
is annotated with the NotDeletable attribute.
* bindings/scripts/test/JS/JSTestActiveDOMObject.cpp: Update the JSC generator test baselines.
* bindings/scripts/test/JS/JSTestCustomNamedGetter.cpp: Ditto.
* bindings/scripts/test/JS/JSTestEventTarget.cpp: Ditto.
* bindings/scripts/test/JS/JSTestInterface.cpp: Ditto.
* bindings/scripts/test/JS/JSTestMediaQueryListListener.cpp: Ditto.
* bindings/scripts/test/JS/JSTestObj.cpp: Ditto.
* bindings/scripts/test/JS/JSTestTypedefs.cpp: Ditto.
* dom/Node.idl: Remove the OperationsNotDeletable attribute.
2013-11-11 Brady Eidson <beidson@apple.com>
Make IDBTransaction tasks asynchronous
......
......@@ -1563,7 +1563,8 @@ sub GenerateImplementation
push(@hashValue2, $functionLength);
my @specials = ();
push(@specials, "DontDelete") unless $function->signature->extendedAttributes->{"Deletable"};
push(@specials, "DontDelete") if $interface->extendedAttributes->{"OperationsNotDeletable"}
|| $function->signature->extendedAttributes->{"NotDeletable"};
push(@specials, "DontEnum") if $function->signature->extendedAttributes->{"NotEnumerable"};
push(@specials, "JSC::Function");
my $special = (@specials > 0) ? join(" | ", @specials) : "0";
......@@ -1627,7 +1628,8 @@ sub GenerateImplementation
push(@hashValue2, $functionLength);
my @specials = ();
push(@specials, "DontDelete") unless $function->signature->extendedAttributes->{"Deletable"};
push(@specials, "DontDelete") if $interface->extendedAttributes->{"OperationsNotDeletable"}
|| $function->signature->extendedAttributes->{"NotDeletable"};
push(@specials, "DontEnum") if $function->signature->extendedAttributes->{"NotEnumerable"};
push(@specials, "JSC::Function");
my $special = (@specials > 0) ? join(" | ", @specials) : "0";
......
......@@ -74,8 +74,8 @@ bool JSTestActiveDOMObjectConstructor::getOwnPropertySlot(JSObject* object, Exec
static const HashTableValue JSTestActiveDOMObjectPrototypeTableValues[] =
{
{ "excitingFunction", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestActiveDOMObjectPrototypeFunctionExcitingFunction), (intptr_t)1 },
{ "postMessage", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestActiveDOMObjectPrototypeFunctionPostMessage), (intptr_t)1 },
{ "excitingFunction", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestActiveDOMObjectPrototypeFunctionExcitingFunction), (intptr_t)1 },
{ "postMessage", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestActiveDOMObjectPrototypeFunctionPostMessage), (intptr_t)1 },
{ 0, 0, NoIntrinsic, 0, 0 }
};
......
......@@ -73,7 +73,7 @@ bool JSTestCustomNamedGetterConstructor::getOwnPropertySlot(JSObject* object, Ex
static const HashTableValue JSTestCustomNamedGetterPrototypeTableValues[] =
{
{ "anotherFunction", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestCustomNamedGetterPrototypeFunctionAnotherFunction), (intptr_t)1 },
{ "anotherFunction", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestCustomNamedGetterPrototypeFunctionAnotherFunction), (intptr_t)1 },
{ 0, 0, NoIntrinsic, 0, 0 }
};
......
......@@ -79,10 +79,10 @@ bool JSTestEventTargetConstructor::getOwnPropertySlot(JSObject* object, ExecStat
static const HashTableValue JSTestEventTargetPrototypeTableValues[] =
{
{ "item", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestEventTargetPrototypeFunctionItem), (intptr_t)1 },
{ "addEventListener", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestEventTargetPrototypeFunctionAddEventListener), (intptr_t)2 },
{ "removeEventListener", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestEventTargetPrototypeFunctionRemoveEventListener), (intptr_t)2 },
{ "dispatchEvent", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestEventTargetPrototypeFunctionDispatchEvent), (intptr_t)1 },
{ "item", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestEventTargetPrototypeFunctionItem), (intptr_t)1 },
{ "addEventListener", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestEventTargetPrototypeFunctionAddEventListener), (intptr_t)2 },
{ "removeEventListener", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestEventTargetPrototypeFunctionRemoveEventListener), (intptr_t)2 },
{ "dispatchEvent", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestEventTargetPrototypeFunctionDispatchEvent), (intptr_t)1 },
{ 0, 0, NoIntrinsic, 0, 0 }
};
......
......@@ -109,10 +109,10 @@ static const HashTableValue JSTestInterfaceConstructorTableValues[] =
{ "supplementalStaticAttr", DontDelete, NoIntrinsic, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsTestInterfaceConstructorSupplementalStaticAttr), (intptr_t)setJSTestInterfaceConstructorSupplementalStaticAttr },
#endif
#if ENABLE(Condition22) || ENABLE(Condition23)
{ "implementsMethod4", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfaceConstructorFunctionImplementsMethod4), (intptr_t)0 },
{ "implementsMethod4", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfaceConstructorFunctionImplementsMethod4), (intptr_t)0 },
#endif
#if ENABLE(Condition11) || ENABLE(Condition12)
{ "supplementalMethod4", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfaceConstructorFunctionSupplementalMethod4), (intptr_t)0 },
{ "supplementalMethod4", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfaceConstructorFunctionSupplementalMethod4), (intptr_t)0 },
#endif
{ 0, 0, NoIntrinsic, 0, 0 }
};
......@@ -200,22 +200,22 @@ static const HashTableValue JSTestInterfacePrototypeTableValues[] =
{ "SUPPLEMENTALCONSTANT2", DontDelete | ReadOnly, NoIntrinsic, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsTestInterfaceSUPPLEMENTALCONSTANT2), (intptr_t)0 },
#endif
#if ENABLE(Condition22) || ENABLE(Condition23)
{ "implementsMethod1", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfacePrototypeFunctionImplementsMethod1), (intptr_t)0 },
{ "implementsMethod1", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfacePrototypeFunctionImplementsMethod1), (intptr_t)0 },
#endif
#if ENABLE(Condition22) || ENABLE(Condition23)
{ "implementsMethod2", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfacePrototypeFunctionImplementsMethod2), (intptr_t)2 },
{ "implementsMethod2", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfacePrototypeFunctionImplementsMethod2), (intptr_t)2 },
#endif
#if ENABLE(Condition22) || ENABLE(Condition23)
{ "implementsMethod3", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfacePrototypeFunctionImplementsMethod3), (intptr_t)0 },
{ "implementsMethod3", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfacePrototypeFunctionImplementsMethod3), (intptr_t)0 },
#endif
#if ENABLE(Condition11) || ENABLE(Condition12)
{ "supplementalMethod1", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfacePrototypeFunctionSupplementalMethod1), (intptr_t)0 },
{ "supplementalMethod1", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfacePrototypeFunctionSupplementalMethod1), (intptr_t)0 },
#endif
#if ENABLE(Condition11) || ENABLE(Condition12)
{ "supplementalMethod2", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfacePrototypeFunctionSupplementalMethod2), (intptr_t)2 },
{ "supplementalMethod2", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfacePrototypeFunctionSupplementalMethod2), (intptr_t)2 },
#endif
#if ENABLE(Condition11) || ENABLE(Condition12)
{ "supplementalMethod3", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfacePrototypeFunctionSupplementalMethod3), (intptr_t)0 },
{ "supplementalMethod3", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestInterfacePrototypeFunctionSupplementalMethod3), (intptr_t)0 },
#endif
{ 0, 0, NoIntrinsic, 0, 0 }
};
......
......@@ -73,7 +73,7 @@ bool JSTestMediaQueryListListenerConstructor::getOwnPropertySlot(JSObject* objec
static const HashTableValue JSTestMediaQueryListListenerPrototypeTableValues[] =
{
{ "method", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestMediaQueryListListenerPrototypeFunctionMethod), (intptr_t)1 },
{ "method", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestMediaQueryListListenerPrototypeFunctionMethod), (intptr_t)1 },
{ 0, 0, NoIntrinsic, 0, 0 }
};
......
......@@ -112,16 +112,16 @@ ConstructType JSTestTypedefsConstructor::getConstructData(JSCell*, ConstructData
static const HashTableValue JSTestTypedefsPrototypeTableValues[] =
{
{ "func", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionFunc), (intptr_t)0 },
{ "setShadow", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionSetShadow), (intptr_t)3 },
{ "methodWithSequenceArg", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionMethodWithSequenceArg), (intptr_t)1 },
{ "nullableArrayArg", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionNullableArrayArg), (intptr_t)1 },
{ "funcWithClamp", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionFuncWithClamp), (intptr_t)1 },
{ "immutablePointFunction", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionImmutablePointFunction), (intptr_t)0 },
{ "stringArrayFunction", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionStringArrayFunction), (intptr_t)1 },
{ "stringArrayFunction2", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionStringArrayFunction2), (intptr_t)1 },
{ "callWithSequenceThatRequiresInclude", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionCallWithSequenceThatRequiresInclude), (intptr_t)1 },
{ "methodWithException", DontDelete | JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionMethodWithException), (intptr_t)0 },
{ "func", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionFunc), (intptr_t)0 },
{ "setShadow", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionSetShadow), (intptr_t)3 },
{ "methodWithSequenceArg", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionMethodWithSequenceArg), (intptr_t)1 },
{ "nullableArrayArg", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionNullableArrayArg), (intptr_t)1 },
{ "funcWithClamp", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionFuncWithClamp), (intptr_t)1 },
{ "immutablePointFunction", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionImmutablePointFunction), (intptr_t)0 },
{ "stringArrayFunction", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionStringArrayFunction), (intptr_t)1 },
{ "stringArrayFunction2", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionStringArrayFunction2), (intptr_t)1 },
{ "callWithSequenceThatRequiresInclude", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionCallWithSequenceThatRequiresInclude), (intptr_t)1 },
{ "methodWithException", JSC::Function, NoIntrinsic, (intptr_t)static_cast<NativeFunction>(jsTestTypedefsPrototypeFunctionMethodWithException), (intptr_t)0 },
{ 0, 0, NoIntrinsic, 0, 0 }
};
......
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