зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1111243 - Implement ES6 proxy behavior for IsArray. r=efaust
This commit is contained in:
Родитель
626a37b7e9
Коммит
c70b84f630
|
@ -84,6 +84,8 @@ const IDB = {
|
|||
add: function(project) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
project = JSON.parse(JSON.stringify(project));
|
||||
|
||||
if (!project.location) {
|
||||
// We need to make sure this object has a `.location` property.
|
||||
deferred.reject("Missing location property on project object.");
|
||||
|
|
|
@ -584,7 +584,11 @@ Valueify(const JSClass *c)
|
|||
enum ESClassValue {
|
||||
ESClass_Object, ESClass_Array, ESClass_Number, ESClass_String,
|
||||
ESClass_Boolean, ESClass_RegExp, ESClass_ArrayBuffer, ESClass_SharedArrayBuffer,
|
||||
ESClass_Date, ESClass_Set, ESClass_Map
|
||||
ESClass_Date, ESClass_Set, ESClass_Map,
|
||||
|
||||
// Special snowflake for the ES6 IsArray method.
|
||||
// Please don't use it without calling that function.
|
||||
ESClass_IsArray
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -2676,7 +2676,8 @@ js::array_concat(JSContext *cx, unsigned argc, Value *vp)
|
|||
HandleValue v = HandleValue::fromMarkedLocation(&p[i]);
|
||||
if (v.isObject()) {
|
||||
RootedObject obj(cx, &v.toObject());
|
||||
if (ObjectClassIs(obj, ESClass_Array, cx)) {
|
||||
// This should be IsConcatSpreadable
|
||||
if (IsArray(obj, cx)) {
|
||||
uint32_t alength;
|
||||
if (!GetLengthProperty(cx, obj, &alength))
|
||||
return false;
|
||||
|
@ -3039,7 +3040,11 @@ static bool
|
|||
array_isArray(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
bool isArray = args.length() > 0 && IsObjectWithClass(args[0], ESClass_Array, cx);
|
||||
bool isArray = false;
|
||||
if (args.get(0).isObject()) {
|
||||
RootedObject obj(cx, &args[0].toObject());
|
||||
isArray = IsArray(obj, cx);
|
||||
}
|
||||
args.rval().setBoolean(isArray);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -719,7 +719,10 @@ ObjectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx)
|
|||
|
||||
switch (classValue) {
|
||||
case ESClass_Object: return obj->is<PlainObject>();
|
||||
case ESClass_Array: return obj->is<ArrayObject>();
|
||||
case ESClass_Array:
|
||||
case ESClass_IsArray:
|
||||
// There difference between those is only relevant for proxies.
|
||||
return obj->is<ArrayObject>();
|
||||
case ESClass_Number: return obj->is<NumberObject>();
|
||||
case ESClass_String: return obj->is<StringObject>();
|
||||
case ESClass_Boolean: return obj->is<BooleanObject>();
|
||||
|
@ -742,6 +745,16 @@ IsObjectWithClass(const Value &v, ESClassValue classValue, JSContext *cx)
|
|||
return ObjectClassIs(obj, classValue, cx);
|
||||
}
|
||||
|
||||
// ES6 7.2.2
|
||||
inline bool
|
||||
IsArray(HandleObject obj, JSContext *cx)
|
||||
{
|
||||
if (obj->is<ArrayObject>())
|
||||
return true;
|
||||
|
||||
return ObjectClassIs(obj, ESClass_IsArray, cx);
|
||||
}
|
||||
|
||||
inline bool
|
||||
Unbox(JSContext *cx, HandleObject obj, MutableHandleValue vp)
|
||||
{
|
||||
|
|
|
@ -322,7 +322,7 @@ JO(JSContext *cx, HandleObject obj, StringifyContext *scx)
|
|||
Maybe<AutoIdVector> ids;
|
||||
const AutoIdVector *props;
|
||||
if (scx->replacer && !scx->replacer->isCallable()) {
|
||||
MOZ_ASSERT(JS_IsArrayObject(cx, scx->replacer));
|
||||
MOZ_ASSERT(IsArray(scx->replacer, cx));
|
||||
props = &scx->propertyList;
|
||||
} else {
|
||||
MOZ_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0);
|
||||
|
@ -507,7 +507,7 @@ Str(JSContext *cx, const Value &v, StringifyContext *scx)
|
|||
|
||||
scx->depth++;
|
||||
bool ok;
|
||||
if (ObjectClassIs(obj, ESClass_Array, cx))
|
||||
if (IsArray(obj, cx))
|
||||
ok = JA(cx, obj, scx);
|
||||
else
|
||||
ok = JO(cx, obj, scx);
|
||||
|
@ -529,7 +529,7 @@ js_Stringify(JSContext *cx, MutableHandleValue vp, JSObject *replacer_, Value sp
|
|||
if (replacer) {
|
||||
if (replacer->isCallable()) {
|
||||
/* Step 4a(i): use replacer to transform values. */
|
||||
} else if (ObjectClassIs(replacer, ESClass_Array, cx)) {
|
||||
} else if (IsArray(replacer, cx)) {
|
||||
/*
|
||||
* Step 4b: The spec algorithm is unhelpfully vague about the exact
|
||||
* steps taken when the replacer is an array, regarding the exact
|
||||
|
@ -560,7 +560,8 @@ js_Stringify(JSContext *cx, MutableHandleValue vp, JSObject *replacer_, Value sp
|
|||
|
||||
/* Step 4b(ii). */
|
||||
uint32_t len;
|
||||
JS_ALWAYS_TRUE(GetLengthProperty(cx, replacer, &len));
|
||||
if (!GetLengthProperty(cx, replacer, &len))
|
||||
return false;
|
||||
if (replacer->is<ArrayObject>() && !replacer->isIndexed())
|
||||
len = Min(len, replacer->as<ArrayObject>().getDenseInitializedLength());
|
||||
|
||||
|
@ -693,7 +694,7 @@ Walk(JSContext *cx, HandleObject holder, HandleId name, HandleValue reviver, Mut
|
|||
if (val.isObject()) {
|
||||
RootedObject obj(cx, &val.toObject());
|
||||
|
||||
if (ObjectClassIs(obj, ESClass_Array, cx)) {
|
||||
if (IsArray(obj, cx)) {
|
||||
/* Step 2a(ii). */
|
||||
uint32_t length;
|
||||
if (!GetLengthProperty(cx, obj, &length))
|
||||
|
|
|
@ -1094,6 +1094,50 @@ ScriptedDirectProxyHandler::construct(JSContext *cx, HandleObject proxy, const C
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ScriptedDirectProxyHandler::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
|
||||
CallArgs args) const
|
||||
{
|
||||
ReportIncompatible(cx, args);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ScriptedDirectProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue,
|
||||
JSContext *cx) const
|
||||
{
|
||||
// Special case IsArray. In every other instance ES wants to have exactly
|
||||
// one object type and not a proxy around it, so return false.
|
||||
if (classValue != ESClass_IsArray)
|
||||
return false;
|
||||
|
||||
// In ES6 IsArray is supposed to poke at the Proxy target, instead we do this here.
|
||||
// The reason for this is that we have proxies for which looking at the target might
|
||||
// be impossible. So instead we use our little objectClassIs function that just works
|
||||
// already across different wrappers.
|
||||
RootedObject target(cx, proxy->as<ProxyObject>().target());
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
return IsArray(target, cx);
|
||||
}
|
||||
|
||||
bool
|
||||
ScriptedDirectProxyHandler::regexp_toShared(JSContext *cx, HandleObject proxy,
|
||||
RegExpGuard *g) const
|
||||
{
|
||||
MOZ_CRASH("Should not end up in ScriptedDirectProxyHandler::regexp_toShared");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ScriptedDirectProxyHandler::boxedValue_unbox(JSContext *cx, HandleObject proxy,
|
||||
MutableHandleValue vp) const
|
||||
{
|
||||
MOZ_CRASH("Should not end up in ScriptedDirectProxyHandler::boxedValue_unbox");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ScriptedDirectProxyHandler::isCallable(JSObject *obj) const
|
||||
{
|
||||
|
|
|
@ -64,6 +64,16 @@ class ScriptedDirectProxyHandler : public DirectProxyHandler {
|
|||
return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props);
|
||||
}
|
||||
|
||||
// A scripted proxy should not be treated as generic in most contexts.
|
||||
virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
|
||||
CallArgs args) const MOZ_OVERRIDE;
|
||||
virtual bool objectClassIs(HandleObject obj, ESClassValue classValue,
|
||||
JSContext *cx) const MOZ_OVERRIDE;
|
||||
virtual bool regexp_toShared(JSContext *cx, HandleObject proxy,
|
||||
RegExpGuard *g) const MOZ_OVERRIDE;
|
||||
virtual bool boxedValue_unbox(JSContext *cx, HandleObject proxy,
|
||||
MutableHandleValue vp) const MOZ_OVERRIDE;
|
||||
|
||||
virtual bool isCallable(JSObject *obj) const MOZ_OVERRIDE;
|
||||
virtual bool isConstructor(JSObject *obj) const MOZ_OVERRIDE;
|
||||
|
||||
|
|
|
@ -35,14 +35,13 @@ for (var constructor of constructors) {
|
|||
}
|
||||
|
||||
// Throws if `this` isn't a TypedArray.
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
|
||||
new Proxy(new constructor(), {})];
|
||||
invalidReceivers.forEach(invalidReceiver => {
|
||||
assertThrowsInstanceOf(() => {
|
||||
constructor.prototype.entries.call(invalidReceiver);
|
||||
}, TypeError, "Assert that entries fails if this value is not a TypedArray");
|
||||
});
|
||||
// FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
|
||||
constructor.prototype.entries.call(new Proxy(new constructor(), {}));
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
|
|
|
@ -111,14 +111,13 @@ for (var constructor of constructors) {
|
|||
}
|
||||
|
||||
// Throws if `this` isn't a TypedArray.
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
|
||||
new Proxy(new constructor(), {})];
|
||||
invalidReceivers.forEach(invalidReceiver => {
|
||||
assertThrowsInstanceOf(() => {
|
||||
constructor.prototype.every.call(invalidReceiver, () => true);
|
||||
}, TypeError, "Assert that every fails if this value is not a TypedArray");
|
||||
});
|
||||
// FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
|
||||
constructor.prototype.every.call(new Proxy(new constructor(), {}), () => true);
|
||||
|
||||
// Test that the length getter is never called.
|
||||
assertEq(Object.defineProperty(new constructor([1, 2, 3]), "length", {
|
||||
|
@ -237,14 +236,13 @@ for (var constructor of constructors) {
|
|||
}
|
||||
|
||||
// Throws if `this` isn't a TypedArray.
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
|
||||
new Proxy(new constructor(), {})];
|
||||
invalidReceivers.forEach(invalidReceiver => {
|
||||
assertThrowsInstanceOf(() => {
|
||||
constructor.prototype.some.call(invalidReceiver, () => true);
|
||||
}, TypeError, "Assert that some fails if this value is not a TypedArray");
|
||||
});
|
||||
// FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
|
||||
constructor.prototype.some.call(new Proxy(new constructor(), {}), () => false);
|
||||
|
||||
// Test that the length getter is never called.
|
||||
assertEq(Object.defineProperty(new constructor([1, 2, 3]), "length", {
|
||||
|
@ -258,4 +256,4 @@ assertEq(new Float32Array([undefined, , NaN]).some(v => v === v), false);
|
|||
assertEq(new Float64Array([undefined, , NaN]).some(v => v === v), false);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
reportCompare(true, true);
|
||||
|
|
|
@ -53,14 +53,13 @@ for (var constructor of constructors) {
|
|||
}
|
||||
|
||||
// Throws if `this` isn't a TypedArray.
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./]
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
|
||||
new Proxy(new constructor(), {})];
|
||||
invalidReceivers.forEach(invalidReceiver => {
|
||||
assertThrowsInstanceOf(() => {
|
||||
constructor.prototype.fill.call(invalidReceiver, 1);
|
||||
}, TypeError);
|
||||
});
|
||||
// FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
|
||||
constructor.prototype.fill.call(new Proxy(new constructor(), {}));
|
||||
|
||||
// Test that the length getter is never called.
|
||||
Object.defineProperty(new constructor([1, 2, 3]), "length", {
|
||||
|
|
|
@ -35,14 +35,13 @@ for (var constructor of constructors) {
|
|||
}
|
||||
|
||||
// Throws if `this` isn't a TypedArray.
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
|
||||
new Proxy(new constructor(), {})];
|
||||
invalidReceivers.forEach(invalidReceiver => {
|
||||
assertThrowsInstanceOf(() => {
|
||||
constructor.prototype.includes.call(invalidReceiver);
|
||||
}, TypeError, "Assert that reverse fails if this value is not a TypedArray");
|
||||
});
|
||||
// FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
|
||||
constructor.prototype.includes.call(new Proxy(new constructor(), {}));
|
||||
|
||||
// Test that the length getter is never called.
|
||||
assertEq(Object.defineProperty(new constructor([1, 2, 3]), "length", {
|
||||
|
|
|
@ -41,14 +41,13 @@ for (var constructor of constructors) {
|
|||
assertEq(new constructor([1, 2, 1, 2, 1]).indexOf(1, -2), 4);
|
||||
|
||||
// Throws if `this` isn't a TypedArray.
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
|
||||
new Proxy(new constructor(), {})];
|
||||
invalidReceivers.forEach(invalidReceiver => {
|
||||
assertThrowsInstanceOf(() => {
|
||||
constructor.prototype.indexOf.call(invalidReceiver);
|
||||
}, TypeError, "Assert that indexOf fails if this value is not a TypedArray");
|
||||
});
|
||||
// FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
|
||||
constructor.prototype.indexOf.call(new Proxy(new constructor(), {}));
|
||||
|
||||
// test that this.length is never called
|
||||
assertEq(Object.defineProperty(new constructor([0, 1, 2, 3, 5]), "length", {
|
||||
|
@ -94,14 +93,13 @@ for (var constructor of constructors) {
|
|||
assertEq(new constructor([1, 2, 1, 2, 1]).lastIndexOf(1, -2), 2);
|
||||
|
||||
// Throws if `this` isn't a TypedArray.
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
|
||||
new Proxy(new constructor(), {})];
|
||||
invalidReceivers.forEach(invalidReceiver => {
|
||||
assertThrowsInstanceOf(() => {
|
||||
constructor.prototype.lastIndexOf.call(invalidReceiver);
|
||||
}, TypeError, "Assert that lastIndexOf fails if this value is not a TypedArray");
|
||||
});
|
||||
// FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
|
||||
constructor.prototype.lastIndexOf.call(new Proxy(new constructor(), {}));
|
||||
|
||||
// Test that the length getter is never called.
|
||||
assertEq(Object.defineProperty(new constructor([0, 1, 2, 3, 5]), "length", {
|
||||
|
|
|
@ -36,14 +36,13 @@ for (var constructor of constructors) {
|
|||
}
|
||||
|
||||
// Throws if `this` isn't a TypedArray.
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
|
||||
new Proxy(new constructor(), {})];
|
||||
invalidReceivers.forEach(invalidReceiver => {
|
||||
assertThrowsInstanceOf(() => {
|
||||
constructor.prototype.join.call(invalidReceiver);
|
||||
}, TypeError, "Assert that join fails if this value is not a TypedArray");
|
||||
});
|
||||
// FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
|
||||
constructor.prototype.join.call(new Proxy(new constructor(), {}));
|
||||
|
||||
// Test that the length getter is never called.
|
||||
assertEq(Object.defineProperty(new constructor([1, 2, 3]), "length", {
|
||||
|
|
|
@ -35,14 +35,13 @@ for (var constructor of constructors) {
|
|||
}
|
||||
|
||||
// Throws if `this` isn't a TypedArray.
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
|
||||
new Proxy(new constructor(), {})];
|
||||
invalidReceivers.forEach(invalidReceiver => {
|
||||
assertThrowsInstanceOf(() => {
|
||||
constructor.prototype.keys.call(invalidReceiver);
|
||||
}, TypeError, "Assert that keys fails if this value is not a TypedArray");
|
||||
});
|
||||
// FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
|
||||
constructor.prototype.keys.call(new Proxy(new constructor(), {}));
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
|
|
|
@ -87,14 +87,13 @@ for (var constructor of constructors) {
|
|||
}
|
||||
|
||||
// Throws if `this` isn't a TypedArray.
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
|
||||
new Proxy(new constructor(3), {})];
|
||||
invalidReceivers.forEach(invalidReceiver => {
|
||||
assertThrowsInstanceOf(() => {
|
||||
constructor.prototype.reduce.call(invalidReceiver, () => {});
|
||||
}, TypeError, "Assert that reduce fails if this value is not a TypedArray");
|
||||
});
|
||||
// FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
|
||||
constructor.prototype.reduce.call(new Proxy(new constructor(3), {}), () => {});
|
||||
|
||||
// Test that the length getter is never called.
|
||||
assertEq(Object.defineProperty(arr, "length", {
|
||||
|
@ -181,14 +180,13 @@ for (var constructor of constructors) {
|
|||
}
|
||||
|
||||
// Throws if `this` isn't a TypedArray.
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
|
||||
new Proxy(new constructor(3), {})];
|
||||
invalidReceivers.forEach(invalidReceiver => {
|
||||
assertThrowsInstanceOf(() => {
|
||||
constructor.prototype.reduceRight.call(invalidReceiver, () => {});
|
||||
}, TypeError, "Assert that reduceRight fails if this value is not a TypedArray");
|
||||
});
|
||||
// FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
|
||||
constructor.prototype.reduceRight.call(new Proxy(new constructor(3), {}), () => {});
|
||||
|
||||
// Test that the length getter is never called.
|
||||
assertEq(Object.defineProperty(arr, "length", {
|
||||
|
@ -199,4 +197,4 @@ for (var constructor of constructors) {
|
|||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
reportCompare(true, true);
|
||||
|
|
|
@ -30,14 +30,13 @@ for (var constructor of constructors) {
|
|||
}
|
||||
|
||||
// Throws if `this` isn't a TypedArray.
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
|
||||
new Proxy(new constructor(), {})];
|
||||
invalidReceivers.forEach(invalidReceiver => {
|
||||
assertThrowsInstanceOf(() => {
|
||||
constructor.prototype.reverse.call(invalidReceiver);
|
||||
}, TypeError, "Assert that reverse fails if this value is not a TypedArray");
|
||||
});
|
||||
// FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
|
||||
constructor.prototype.reverse.call(new Proxy(new constructor(), {}));
|
||||
|
||||
// Test that the length getter is never called.
|
||||
Object.defineProperty(new constructor([1, 2, 3]), "length", {
|
||||
|
|
|
@ -36,14 +36,13 @@ for (var constructor of constructors) {
|
|||
}
|
||||
|
||||
// Throws if `this` isn't a TypedArray.
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
|
||||
var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
|
||||
new Proxy(new constructor(), {})];
|
||||
invalidReceivers.forEach(invalidReceiver => {
|
||||
assertThrowsInstanceOf(() => {
|
||||
constructor.prototype.values.call(invalidReceiver);
|
||||
}, TypeError, "Assert that values fails if this value is not a TypedArray");
|
||||
});
|
||||
// FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
|
||||
constructor.prototype.values.call(new Proxy(new constructor(), {}));
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
|
|
|
@ -489,6 +489,7 @@ RegExpToShared(JSContext *cx, HandleObject obj, RegExpGuard *g)
|
|||
{
|
||||
if (obj->is<RegExpObject>())
|
||||
return obj->as<RegExpObject>().getShared(cx, g);
|
||||
MOZ_ASSERT(Proxy::objectClassIs(obj, ESClass_RegExp, cx));
|
||||
return Proxy::regexp_toShared(cx, obj, g);
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче