зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1364854 - Port Object.assign to C++. r=evilpie
This commit is contained in:
Родитель
b5f093f0c1
Коммит
bd874203f9
|
@ -593,6 +593,187 @@ obj_setPrototypeOf(JSContext* cx, unsigned argc, Value* vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
PropertyIsEnumerable(JSContext* cx, HandleObject obj, HandleId id, bool* enumerable)
|
||||
{
|
||||
PropertyResult prop;
|
||||
if (obj->isNative() &&
|
||||
NativeLookupOwnProperty<NoGC>(cx, &obj->as<NativeObject>(), id, &prop))
|
||||
{
|
||||
if (!prop) {
|
||||
*enumerable = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned attrs = GetPropertyAttributes(obj, prop);
|
||||
*enumerable = (attrs & JSPROP_ENUMERATE) != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
Rooted<PropertyDescriptor> desc(cx);
|
||||
if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
|
||||
return false;
|
||||
|
||||
*enumerable = desc.object() && desc.enumerable();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
TryAssignNative(JSContext* cx, HandleObject to, HandleObject from, bool* optimized)
|
||||
{
|
||||
*optimized = false;
|
||||
|
||||
if (!from->isNative() || !to->isNative())
|
||||
return true;
|
||||
|
||||
// Don't use the fast path if |from| may have extra indexed or lazy
|
||||
// properties.
|
||||
NativeObject* fromNative = &from->as<NativeObject>();
|
||||
if (fromNative->getDenseInitializedLength() > 0 ||
|
||||
fromNative->isIndexed() ||
|
||||
fromNative->is<TypedArrayObject>() ||
|
||||
fromNative->getClass()->getNewEnumerate() ||
|
||||
fromNative->getClass()->getEnumerate())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get a list of |from| shapes. As long as from->lastProperty() == fromShape
|
||||
// we can use this to speed up both the enumerability check and the GetProp.
|
||||
|
||||
using ShapeVector = GCVector<Shape*, 8>;
|
||||
Rooted<ShapeVector> shapes(cx, ShapeVector(cx));
|
||||
|
||||
RootedShape fromShape(cx, fromNative->lastProperty());
|
||||
for (Shape::Range<NoGC> r(fromShape); !r.empty(); r.popFront()) {
|
||||
// Symbol properties need to be assigned last. For now fall back to the
|
||||
// slow path if we see a symbol property.
|
||||
if (MOZ_UNLIKELY(JSID_IS_SYMBOL(r.front().propidRaw())))
|
||||
return true;
|
||||
if (MOZ_UNLIKELY(!shapes.append(&r.front())))
|
||||
return false;
|
||||
}
|
||||
|
||||
*optimized = true;
|
||||
|
||||
RootedShape shape(cx);
|
||||
RootedValue propValue(cx);
|
||||
RootedId nextKey(cx);
|
||||
RootedValue toReceiver(cx, ObjectValue(*to));
|
||||
|
||||
for (size_t i = shapes.length(); i > 0; i--) {
|
||||
shape = shapes[i - 1];
|
||||
nextKey = shape->propid();
|
||||
|
||||
// Ensure |from| is still native: a getter/setter might have turned
|
||||
// |from| or |to| into an unboxed object or it could have been swapped
|
||||
// with a non-native object.
|
||||
if (MOZ_LIKELY(from->isNative() &&
|
||||
from->as<NativeObject>().lastProperty() == fromShape &&
|
||||
shape->hasDefaultGetter() &&
|
||||
shape->hasSlot()))
|
||||
{
|
||||
if (!shape->enumerable())
|
||||
continue;
|
||||
propValue = from->as<NativeObject>().getSlot(shape->slot());
|
||||
} else {
|
||||
// |from| changed shape or the property is not a data property, so
|
||||
// we have to do the slower enumerability check and GetProp.
|
||||
bool enumerable;
|
||||
if (!PropertyIsEnumerable(cx, from, nextKey, &enumerable))
|
||||
return false;
|
||||
if (!enumerable)
|
||||
continue;
|
||||
if (!GetProperty(cx, from, from, nextKey, &propValue))
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjectOpResult result;
|
||||
if (MOZ_UNLIKELY(!SetProperty(cx, to, nextKey, propValue, toReceiver, result)))
|
||||
return false;
|
||||
if (MOZ_UNLIKELY(!result.checkStrict(cx, to, nextKey)))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
AssignSlow(JSContext* cx, HandleObject to, HandleObject from)
|
||||
{
|
||||
// Step 4.b.ii.
|
||||
AutoIdVector keys(cx);
|
||||
if (!GetPropertyKeys(cx, from, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &keys))
|
||||
return false;
|
||||
|
||||
// Step 4.c.
|
||||
RootedId nextKey(cx);
|
||||
RootedValue propValue(cx);
|
||||
for (size_t i = 0, len = keys.length(); i < len; i++) {
|
||||
nextKey = keys[i];
|
||||
|
||||
// Step 4.c.i.
|
||||
bool enumerable;
|
||||
if (MOZ_UNLIKELY(!PropertyIsEnumerable(cx, from, nextKey, &enumerable)))
|
||||
return false;
|
||||
if (!enumerable)
|
||||
continue;
|
||||
|
||||
// Step 4.c.ii.1.
|
||||
if (MOZ_UNLIKELY(!GetProperty(cx, from, from, nextKey, &propValue)))
|
||||
return false;
|
||||
|
||||
// Step 4.c.ii.2.
|
||||
if (MOZ_UNLIKELY(!SetProperty(cx, to, nextKey, propValue)))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ES2018 draft rev 48ad2688d8f964da3ea8c11163ef20eb126fb8a4
|
||||
// 19.1.2.1 Object.assign(target, ...sources)
|
||||
static bool
|
||||
obj_assign(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1.
|
||||
RootedObject to(cx, ToObject(cx, args.get(0)));
|
||||
if (!to)
|
||||
return false;
|
||||
|
||||
// Note: step 2 is implicit. If there are 0 arguments, ToObject throws. If
|
||||
// there's 1 argument, the loop below is a no-op.
|
||||
|
||||
// Step 4.
|
||||
RootedObject from(cx);
|
||||
for (size_t i = 1; i < args.length(); i++) {
|
||||
// Step 4.a.
|
||||
if (args[i].isNullOrUndefined())
|
||||
continue;
|
||||
|
||||
// Step 4.b.i.
|
||||
from = ToObject(cx, args[i]);
|
||||
if (!from)
|
||||
return false;
|
||||
|
||||
// Steps 4.b.ii, 4.c.
|
||||
bool optimized;
|
||||
if (!TryAssignNative(cx, to, from, &optimized))
|
||||
return false;
|
||||
if (optimized)
|
||||
continue;
|
||||
|
||||
if (!AssignSlow(cx, to, from))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5.
|
||||
args.rval().setObject(*to);
|
||||
return true;
|
||||
}
|
||||
|
||||
#if JS_HAS_OBJ_WATCHPOINT
|
||||
|
||||
bool
|
||||
|
@ -1295,7 +1476,7 @@ static const JSPropertySpec object_properties[] = {
|
|||
};
|
||||
|
||||
static const JSFunctionSpec object_static_methods[] = {
|
||||
JS_SELF_HOSTED_FN("assign", "ObjectStaticAssign", 2, 0),
|
||||
JS_FN("assign", obj_assign, 2, 0),
|
||||
JS_SELF_HOSTED_FN("getPrototypeOf", "ObjectGetPrototypeOf", 1, 0),
|
||||
JS_FN("setPrototypeOf", obj_setPrototypeOf, 2, 0),
|
||||
JS_FN("getOwnPropertyDescriptor", obj_getOwnPropertyDescriptor,2, 0),
|
||||
|
|
|
@ -2,45 +2,6 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// ES6 draft rev36 2015-03-17 19.1.2.1
|
||||
function ObjectStaticAssign(target, firstSource) {
|
||||
// Steps 1-2.
|
||||
var to = ToObject(target);
|
||||
|
||||
// Step 3.
|
||||
if (arguments.length < 2)
|
||||
return to;
|
||||
|
||||
// Steps 4-5.
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
// Step 5.a.
|
||||
var nextSource = arguments[i];
|
||||
if (nextSource === null || nextSource === undefined)
|
||||
continue;
|
||||
|
||||
// Steps 5.b.i-ii.
|
||||
var from = ToObject(nextSource);
|
||||
|
||||
// Steps 5.b.iii-iv.
|
||||
var keys = OwnPropertyKeys(from, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS);
|
||||
|
||||
// Step 5.c.
|
||||
for (var nextIndex = 0, len = keys.length; nextIndex < len; nextIndex++) {
|
||||
var nextKey = keys[nextIndex];
|
||||
|
||||
// Steps 5.c.i-iii. We abbreviate this by calling propertyIsEnumerable
|
||||
// which is faster and returns false for not defined properties.
|
||||
if (callFunction(std_Object_propertyIsEnumerable, from, nextKey)) {
|
||||
// Steps 5.c.iii.1-4.
|
||||
to[nextKey] = from[nextKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6.
|
||||
return to;
|
||||
}
|
||||
|
||||
// ES stage 4 proposal
|
||||
function ObjectGetOwnPropertyDescriptors(O) {
|
||||
// Step 1.
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
function test() {
|
||||
var from, to;
|
||||
|
||||
// Property changes value.
|
||||
from = {x: 1, y: 2};
|
||||
to = {set x(v) { from.y = 5; }};
|
||||
Object.assign(to, from);
|
||||
assertEq(to.y, 5);
|
||||
|
||||
// Property becomes a getter.
|
||||
from = {x: 1, y: 2};
|
||||
to = {set x(v) { Object.defineProperty(from, "y", {get: () => 4}); }};
|
||||
Object.assign(to, from);
|
||||
assertEq(to.y, 4);
|
||||
|
||||
// Property becomes non-enumerable.
|
||||
from = {x: 1, y: 2};
|
||||
to = {set x(v) { Object.defineProperty(from, "y", {value: 2,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: true}); }};
|
||||
Object.assign(to, from);
|
||||
assertEq("y" in to, false);
|
||||
to = {};
|
||||
Object.assign(to, from);
|
||||
assertEq("y" in to, false);
|
||||
|
||||
// Property is deleted. Should NOT get Object.prototype.toString.
|
||||
from = {x: 1, toString: 2};
|
||||
to = {set x(v) { delete from.toString; }};
|
||||
Object.assign(to, from);
|
||||
assertEq(to.hasOwnProperty("toString"), false);
|
||||
|
||||
from = {toString: 2, x: 1};
|
||||
to = {set x(v) { delete from.toString; }};
|
||||
Object.assign(to, from);
|
||||
assertEq(to.toString, 2);
|
||||
|
||||
from = {x: 1, toString: 2, y: 3};
|
||||
to = {set x(v) { delete from.toString; }};
|
||||
Object.assign(to, from);
|
||||
assertEq(to.hasOwnProperty("toString"), false);
|
||||
assertEq(to.y, 3);
|
||||
|
||||
// New property is added.
|
||||
from = {x: 1, y: 2};
|
||||
to = {set x(v) { from.z = 3; }};
|
||||
Object.assign(to, from);
|
||||
assertEq("z" in to, false);
|
||||
|
||||
// From getter.
|
||||
var c = 7;
|
||||
from = {x: 1, get y() { return ++c; }};
|
||||
to = {};
|
||||
Object.assign(to, from);
|
||||
Object.assign(to, from, from);
|
||||
assertEq(to.y, 10);
|
||||
|
||||
// Frozen object.
|
||||
from = {x: 1, y: 2};
|
||||
to = {x: 4};
|
||||
Object.freeze(to);
|
||||
var ex;
|
||||
try {
|
||||
Object.assign(to, from);
|
||||
} catch (e) {
|
||||
ex = e;
|
||||
}
|
||||
assertEq(ex instanceof TypeError, true);
|
||||
assertEq(to.x, 4);
|
||||
|
||||
// Non-writable property.
|
||||
from = {x: 1, y: 2, z: 3};
|
||||
to = {};
|
||||
Object.defineProperty(to, "y", {value: 9, writable: false});
|
||||
ex = null;
|
||||
try {
|
||||
Object.assign(to, from);
|
||||
} catch(e) {
|
||||
ex = e;
|
||||
}
|
||||
assertEq(ex instanceof TypeError, true);
|
||||
assertEq(to.x, 1);
|
||||
assertEq(to.y, 9);
|
||||
assertEq(to.z, undefined);
|
||||
|
||||
// Array with dense elements.
|
||||
from = [1, 2, 3];
|
||||
to = {};
|
||||
Object.assign(to, from);
|
||||
assertEq(to[2], 3);
|
||||
assertEq("length" in to, false);
|
||||
|
||||
// Object with sparse elements and symbols.
|
||||
from = {x: 1, 1234567: 2, 1234560: 3,[Symbol.iterator]: 5, z: 3};
|
||||
to = {};
|
||||
Object.assign(to, from);
|
||||
assertEq(to[1234567], 2);
|
||||
assertEq(Object.keys(to).toString(), "1234560,1234567,x,z");
|
||||
assertEq(to[Symbol.iterator], 5);
|
||||
|
||||
// Symbol properties need to be assigned last.
|
||||
from = {x: 1, [Symbol.iterator]: 2, y: 3};
|
||||
to = {set y(v) { throw 9; }};
|
||||
ex = null;
|
||||
try {
|
||||
Object.assign(to, from);
|
||||
} catch (e) {
|
||||
ex = e;
|
||||
}
|
||||
assertEq(ex, 9);
|
||||
assertEq(to.x, 1);
|
||||
assertEq(to.hasOwnProperty(Symbol.iterator), false);
|
||||
|
||||
// Typed array.
|
||||
from = new Int32Array([1, 2, 3]);
|
||||
to = {};
|
||||
Object.assign(to, from);
|
||||
assertEq(to[1], 2);
|
||||
|
||||
// Primitive string.
|
||||
from = "foo";
|
||||
to = {};
|
||||
Object.assign(to, from);
|
||||
assertEq(to[0], "f");
|
||||
|
||||
// String object.
|
||||
from = new String("bar");
|
||||
to = {};
|
||||
Object.assign(to, from);
|
||||
assertEq(to[2], "r");
|
||||
}
|
||||
test();
|
||||
test();
|
||||
test();
|
Загрузка…
Ссылка в новой задаче