Bug 1036136 - Implement structured cloning for Map and Set objects. r=jorendorff,bent

This commit is contained in:
Tom Schuster 2014-07-19 23:44:53 +02:00
Родитель 3274c04222
Коммит c25e1b9944
8 изменённых файлов: 509 добавлений и 92 удалений

Просмотреть файл

@ -35,11 +35,11 @@ namespace {
// If JS_STRUCTURED_CLONE_VERSION changes then we need to update our major
// schema version.
static_assert(JS_STRUCTURED_CLONE_VERSION == 3,
static_assert(JS_STRUCTURED_CLONE_VERSION == 4,
"Need to update the major schema version.");
// Major schema version. Bump for almost everything.
const uint32_t kMajorSchemaVersion = 15;
const uint32_t kMajorSchemaVersion = 16;
// Minor schema version. Should almost always be 0 (maybe bump on release
// branches if we have to).
@ -1471,6 +1471,17 @@ UpgradeSchemaFrom14_0To15_0(mozIStorageConnection* aConnection)
return NS_OK;
}
nsresult
UpgradeSchemaFrom15_0To16_0(mozIStorageConnection* aConnection)
{
// The only change between 15 and 16 was a different structured
// clone format, but it's backwards-compatible.
nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(16, 0));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
class VersionChangeEventsRunnable;
class SetVersionHelper : public AsyncConnectionHelper,
@ -2088,7 +2099,7 @@ OpenDatabaseHelper::CreateDatabaseConnection(
}
else {
// This logic needs to change next time we change the schema!
static_assert(kSQLiteSchemaVersion == int32_t((15 << 4) + 0),
static_assert(kSQLiteSchemaVersion == int32_t((16 << 4) + 0),
"Need upgrade code from schema version increase.");
while (schemaVersion != kSQLiteSchemaVersion) {
@ -2126,6 +2137,9 @@ OpenDatabaseHelper::CreateDatabaseConnection(
else if (schemaVersion == MakeSchemaVersion(14, 0)) {
rv = UpgradeSchemaFrom14_0To15_0(connection);
}
else if (schemaVersion == MakeSchemaVersion(15, 0)) {
rv = UpgradeSchemaFrom15_0To16_0(connection);
}
else {
NS_WARNING("Unable to open IndexedDB database, no upgrade path is "
"available!");

Просмотреть файл

@ -121,7 +121,7 @@ typedef void (*FreeTransferStructuredCloneOp)(uint32_t tag, JS::TransferableOwne
// The maximum supported structured-clone serialization format version. Note
// that this does not need to be bumped for Transferable-only changes, since
// they are never saved to persistent storage.
#define JS_STRUCTURED_CLONE_VERSION 3
#define JS_STRUCTURED_CLONE_VERSION 4
struct JSStructuredCloneCallbacks {
ReadStructuredCloneOp read;

Просмотреть файл

@ -1156,6 +1156,62 @@ WriteBarrierPost(JSRuntime *rt, ValueSet *set, const Value &key)
#endif
}
bool
MapObject::entries(JSContext *cx, HandleObject obj, JS::AutoValueVector *entries)
{
ValueMap *map = obj->as<MapObject>().getData();
if (!map)
return false;
for (ValueMap::Range r = map->all(); !r.empty(); r.popFront()) {
if (!entries->append(r.front().key.get()) ||
!entries->append(r.front().value))
{
return false;
}
}
return true;
}
bool
MapObject::set(JSContext *cx, HandleObject obj, HandleValue k, HandleValue v)
{
ValueMap *map = obj->as<MapObject>().getData();
if (!map)
return false;
AutoHashableValueRooter key(cx);
if (!key.setValue(cx, k))
return false;
RelocatableValue rval(v);
if (!map->put(key, rval)) {
js_ReportOutOfMemory(cx);
return false;
}
WriteBarrierPost(cx->runtime(), map, key.get());
return true;
}
MapObject*
MapObject::create(JSContext *cx)
{
RootedObject obj(cx, NewBuiltinClassInstance(cx, &class_));
if (!obj)
return nullptr;
ValueMap *map = cx->new_<ValueMap>(cx->runtime());
if (!map || !map->init()) {
js_delete(map);
js_ReportOutOfMemory(cx);
return nullptr;
}
obj->setPrivate(map);
return &obj->as<MapObject>();
}
void
MapObject::finalize(FreeOp *fop, JSObject *obj)
{
@ -1166,18 +1222,10 @@ MapObject::finalize(FreeOp *fop, JSObject *obj)
bool
MapObject::construct(JSContext *cx, unsigned argc, Value *vp)
{
Rooted<JSObject*> obj(cx, NewBuiltinClassInstance(cx, &class_));
Rooted<MapObject*> obj(cx, MapObject::create(cx));
if (!obj)
return false;
ValueMap *map = cx->new_<ValueMap>(cx->runtime());
if (!map || !map->init()) {
js_delete(map);
js_ReportOutOfMemory(cx);
return false;
}
obj->setPrivate(map);
CallArgs args = CallArgsFromVp(argc, vp);
if (args.hasDefined(0)) {
ForOfIterator iter(cx);
@ -1185,6 +1233,7 @@ MapObject::construct(JSContext *cx, unsigned argc, Value *vp)
return false;
RootedValue pairVal(cx);
RootedObject pairObj(cx);
ValueMap *map = obj->getData();
while (true) {
bool done;
if (!iter.next(&pairVal, &done))
@ -1654,6 +1703,58 @@ SetObject::initClass(JSContext *cx, JSObject *obj)
return proto;
}
bool
SetObject::keys(JSContext *cx, HandleObject obj, JS::AutoValueVector *keys)
{
ValueSet *set = obj->as<SetObject>().getData();
if (!set)
return false;
for (ValueSet::Range r = set->all(); !r.empty(); r.popFront()) {
if (!keys->append(r.front().get()))
return false;
}
return true;
}
bool
SetObject::add(JSContext *cx, HandleObject obj, HandleValue k)
{
ValueSet *set = obj->as<SetObject>().getData();
if (!set)
return false;
AutoHashableValueRooter key(cx);
if (!key.setValue(cx, k))
return false;
if (!set->put(key)) {
js_ReportOutOfMemory(cx);
return false;
}
WriteBarrierPost(cx->runtime(), set, key.get());
return true;
}
SetObject*
SetObject::create(JSContext *cx)
{
RootedObject obj(cx, NewBuiltinClassInstance(cx, &class_));
if (!obj)
return nullptr;
ValueSet *set = cx->new_<ValueSet>(cx->runtime());
if (!set || !set->init()) {
js_delete(set);
js_ReportOutOfMemory(cx);
return nullptr;
}
obj->setPrivate(set);
return &obj->as<SetObject>();
}
void
SetObject::mark(JSTracer *trc, JSObject *obj)
{
@ -1675,18 +1776,10 @@ SetObject::finalize(FreeOp *fop, JSObject *obj)
bool
SetObject::construct(JSContext *cx, unsigned argc, Value *vp)
{
Rooted<JSObject*> obj(cx, NewBuiltinClassInstance(cx, &class_));
Rooted<SetObject*> obj(cx, SetObject::create(cx));
if (!obj)
return false;
ValueSet *set = cx->new_<ValueSet>(cx->runtime());
if (!set || !set->init()) {
js_delete(set);
js_ReportOutOfMemory(cx);
return false;
}
obj->setPrivate(set);
CallArgs args = CallArgsFromVp(argc, vp);
if (args.hasDefined(0)) {
RootedValue keyVal(cx);
@ -1694,6 +1787,7 @@ SetObject::construct(JSContext *cx, unsigned argc, Value *vp)
if (!iter.init(args[0]))
return false;
AutoHashableValueRooter key(cx);
ValueSet *set = obj->getData();
while (true) {
bool done;
if (!iter.next(&keyVal, &done))

Просмотреть файл

@ -91,6 +91,12 @@ class MapObject : public JSObject {
static JSObject *initClass(JSContext *cx, JSObject *obj);
static const Class class_;
// Entries is every key followed by value.
static bool entries(JSContext *cx, HandleObject obj, JS::AutoValueVector *entries);
static bool set(JSContext *cx, HandleObject obj, HandleValue key, HandleValue value);
static MapObject* create(JSContext *cx);
private:
static const JSPropertySpec properties[];
static const JSFunctionSpec methods[];
@ -129,6 +135,11 @@ class SetObject : public JSObject {
enum IteratorKind { Values, Entries };
static JSObject *initClass(JSContext *cx, JSObject *obj);
static const Class class_;
static bool keys(JSContext *cx, HandleObject obj, JS::AutoValueVector *keys);
static bool add(JSContext *cx, HandleObject obj, HandleValue key);
static SetObject* create(JSContext *cx);
private:
static const JSPropertySpec properties[];
static const JSFunctionSpec methods[];

Просмотреть файл

@ -0,0 +1,110 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
var map = new Map();
map.set("self", map);
var magic = deserialize(serialize(map));
assertEq(magic.get("self"), magic);
assertEq(magic.size, 1);
map = new Map();
map.set(map, "self");
magic = deserialize(serialize(map));
assertEq(magic.get(magic), "self");
assertEq(magic.size, 1);
var values = [
"a", "\uDEFF", undefined, null, -3.5, true, false, NaN, 155, -2
]
map = new Map();
for (var value of values) {
map.set(value, value);
}
magic = deserialize(serialize(map));
var i = 0;
for (value of magic) {
assertEq(value[0], value[1]);
assertEq(value[0], values[i++]);
}
assertEq([...map.keys()].toSource(), [...magic.keys()].toSource());
assertEq([...map.values()].toSource(), [...magic.values()].toSource());
var obj = {a: 1};
obj.map = new Map();
obj.map.set("obj", obj);
magic = deserialize(serialize(obj));
assertEq(magic.map.get("obj"), magic);
assertEq(magic.a, 1);
map = new Map();
map.set("a", new Number(1));
map.set("b", new String("aaaa"));
map.set("c", new Date(NaN));
magic = deserialize(serialize(map));
assertEq(magic.get("a").valueOf(), 1);
assertEq(magic.get("b").valueOf(), "aaaa");
assertEq(magic.get("c").valueOf(), NaN);
assertEq([...magic.keys()].toSource(), ["a", "b", "c"].toSource());
map = new Map();
map.set("x", new Map());
map.get("x").set("x", map);
map.get("x").set("b", null);
magic = deserialize(serialize(map));
assertEq(magic.get("x").get("x"), magic);
assertEq(magic.get("x").get("b"), null);
map = new Map()
map.set({a: 1}, "b");
magic = deserialize(serialize(map));
obj = [...magic.keys()][0];
assertEq(obj.a, 1);
assertEq(magic.get(obj), "b");
// Make sure expandos aren't cloned (Bug 1041172)
map = new Map();
map.a = "aaaaa";
magic = deserialize(serialize(map));
assertEq("a" in magic, false);
assertEq(Object.keys(magic).length, 0);
// Busted [[Prototype]] shouldn't matter
map = new Map();
Object.setPrototypeOf(map, null);
Map.prototype.set.call(map, "self", map);
magic = deserialize(serialize(map));
assertEq(magic.get("self"), magic);
assertEq(magic.size, 1);
// Can't fuzz around with Map after it is cloned
obj = {
a: new Map(),
get b() {
obj.a.delete("test");
return "invoked";
}
}
obj.a.set("test", "hello");
assertEq(obj.a.has("test"), true);
magic = deserialize(serialize(obj));
assertEq(obj.a.has("test"), false);
assertEq(magic.a.size, 1);
assertEq(magic.a.get("test"), "hello");
assertEq([...magic.a.keys()].toString(), "test");
assertEq(magic.b, "invoked");

Просмотреть файл

@ -0,0 +1,82 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
var set = new Set();
set.add(set);
var magic = deserialize(serialize(set));
assertEq(magic.size, 1);
assertEq(magic.values().next().value, magic);
var values = [
"a", "\uDEFF", undefined, null, -3.5, true, false, NaN, 155, -2
]
set = new Set();
for (var value of values) {
set.add(value)
}
magic = deserialize(serialize(set));
var i = 0;
for (value of magic) {
assertEq(value, values[i++]);
}
assertEq([...set.keys()].toSource(), [...magic.keys()].toSource());
assertEq([...set.values()].toSource(), [...magic.values()].toSource());
var obj = {a: 1};
obj.set = new Set();
obj.set.add(obj);
magic = deserialize(serialize(obj));
assertEq(magic.set.values().next().value, magic);
assertEq(magic.a, 1);
set = new Set();
set.add(new Number(1));
set.add(new String("aaaa"));
set.add(new Date(NaN));
magic = deserialize(serialize(set));
values = magic.values();
assertEq(values.next().value.valueOf(), 1);
assertEq(values.next().value.valueOf(), "aaaa");
assertEq(values.next().value.valueOf(), NaN);
assertEq(values.next().done, true);
// Make sure expandos aren't cloned (Bug 1041172)
set = new Set();
set.a = "aaaaa";
magic = deserialize(serialize(set));
assertEq("a" in magic, false);
assertEq(Object.keys(magic).length, 0);
// Busted [[Prototype]] shouldn't matter
set = new Set();
Object.setPrototypeOf(set, null);
Set.prototype.add.call(set, "aaa");
magic = deserialize(serialize(set));
assertEq(magic.has("aaa"), true);
assertEq(magic.size, 1);
// Can't fuzz around with Set after it is cloned
obj = {
a: new Set(),
get b() {
obj.a.delete("test");
return "invoked";
}
}
obj.a.add("test");
assertEq(obj.a.has("test"), true);
magic = deserialize(serialize(obj));
assertEq(obj.a.has("test"), false);
assertEq(magic.a.size, 1);
assertEq([...magic.a.keys()].toString(), "test");
assertEq(magic.b, "invoked");

Просмотреть файл

@ -0,0 +1,13 @@
// Created with JS_STRUCTURED_CLONE_VERSION = 3
// var x = {
// "ab": 1,
// 12: 2,
// };
// print(uneval(serialize(x).clonebuffer));
var clonebuffer = serialize("abc");
clonebuffer.clonebuffer = "\x00\x00\x00\x00\b\x00\xFF\xFF\f\x00\x00\x00\x03\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\x00@\x02\x00\x00\x00\x04\x00\xFF\xFFa\x00b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xF0?\x00\x00\x00\x00\x00\x00\xFF\xFF"
var obj = deserialize(clonebuffer)
assertEq(obj.ab, 1);
assertEq(obj[12], 2);
assertEq(Object.keys(obj).toString(), "12,ab");

Просмотреть файл

@ -40,6 +40,7 @@
#include "jsdate.h"
#include "jswrapper.h"
#include "builtin/MapObject.h"
#include "vm/SharedArrayObject.h"
#include "vm/TypedArrayObject.h"
#include "vm/WrapperObject.h"
@ -60,7 +61,7 @@ enum StructuredDataType MOZ_ENUM_TYPE(uint32_t) {
SCTAG_NULL = 0xFFFF0000,
SCTAG_UNDEFINED,
SCTAG_BOOLEAN,
SCTAG_INDEX,
SCTAG_INT32,
SCTAG_STRING,
SCTAG_DATE_OBJECT,
SCTAG_REGEXP_OBJECT,
@ -74,6 +75,10 @@ enum StructuredDataType MOZ_ENUM_TYPE(uint32_t) {
SCTAG_DO_NOT_USE_1, // Required for backwards compatibility
SCTAG_DO_NOT_USE_2, // Required for backwards compatibility
SCTAG_TYPED_ARRAY_OBJECT,
SCTAG_MAP_OBJECT,
SCTAG_SET_OBJECT,
SCTAG_END_OF_KEYS,
SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100,
SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int8,
SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8,
@ -222,7 +227,6 @@ struct JSStructuredCloneReader {
bool readTypedArray(uint32_t arrayType, uint32_t nelems, Value *vp, bool v1Read = false);
bool readArrayBuffer(uint32_t nbytes, Value *vp);
bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, Value *vp);
bool readId(jsid *idp);
bool startRead(Value *vp);
SCInput &in;
@ -249,7 +253,7 @@ struct JSStructuredCloneWriter {
void *cbClosure,
jsval tVal)
: out(cx), objs(out.context()),
counts(out.context()), ids(out.context()),
counts(out.context()), entries(out.context()),
memory(out.context()), callbacks(cb), closure(cbClosure),
transferable(out.context(), tVal), transferableObjects(out.context()) { }
@ -271,12 +275,13 @@ struct JSStructuredCloneWriter {
bool writeTransferMap();
bool writeString(uint32_t tag, JSString *str);
bool writeId(jsid id);
bool writeArrayBuffer(HandleObject obj);
bool writeTypedArray(HandleObject obj);
bool startObject(HandleObject obj, bool *backref);
bool startWrite(const Value &v);
bool traverseObject(HandleObject obj);
bool traverseMap(HandleObject obj);
bool traverseSet(HandleObject obj);
bool parseTransferable();
bool reportErrorTransferable();
@ -292,12 +297,14 @@ struct JSStructuredCloneWriter {
// entered before any manipulation is performed.
AutoValueVector objs;
// counts[i] is the number of properties of objs[i] remaining to be written.
// counts.length() == objs.length() and sum(counts) == ids.length().
// counts[i] is the number of entries of objs[i] remaining to be written.
// counts.length() == objs.length() and sum(counts) == entries.length().
Vector<size_t> counts;
// Ids of properties remaining to be written.
AutoIdVector ids;
// For JSObject: Propery IDs as value
// For Map: Key followed by value.
// For Set: Key
AutoValueVector entries;
// The "memory" list described in the HTML5 internal structured cloning algorithm.
// memory is a superset of objs; items are never removed from Memory
@ -811,15 +818,6 @@ JSStructuredCloneWriter::writeString(uint32_t tag, JSString *str)
: out.writeChars(linear->twoByteChars(nogc), length);
}
bool
JSStructuredCloneWriter::writeId(jsid id)
{
if (JSID_IS_INT(id))
return out.writePair(SCTAG_INDEX, uint32_t(JSID_TO_INT(id)));
JS_ASSERT(JSID_IS_STRING(id));
return writeString(SCTAG_STRING, JSID_TO_STRING(id));
}
inline void
JSStructuredCloneWriter::checkStack()
{
@ -835,9 +833,9 @@ JSStructuredCloneWriter::checkStack()
total += counts[i];
}
if (counts.length() <= MAX)
JS_ASSERT(total == ids.length());
JS_ASSERT(total == entries.length());
else
JS_ASSERT(total <= ids.length());
JS_ASSERT(total <= entries.length());
size_t j = objs.length();
for (size_t i = 0; i < limit; i++)
@ -911,22 +909,71 @@ JSStructuredCloneWriter::traverseObject(HandleObject obj)
* Get enumerable property ids and put them in reverse order so that they
* will come off the stack in forward order.
*/
size_t initialLength = ids.length();
if (!GetPropertyNames(context(), obj, JSITER_OWNONLY, &ids))
AutoIdVector properties(context());
if (!GetPropertyNames(context(), obj, JSITER_OWNONLY, &properties))
return false;
jsid *begin = ids.begin() + initialLength, *end = ids.end();
size_t count = size_t(end - begin);
Reverse(begin, end);
for (size_t i = properties.length(); i > 0; --i) {
MOZ_ASSERT(JSID_IS_STRING(properties[i - 1]) || JSID_IS_INT(properties[i - 1]));
RootedValue val(context(), IdToValue(properties[i - 1]));
if (!entries.append(val))
return false;
}
/* Push obj and count to the stack. */
if (!objs.append(ObjectValue(*obj)) || !counts.append(count))
if (!objs.append(ObjectValue(*obj)) || !counts.append(properties.length()))
return false;
checkStack();
/* Write the header for obj. */
return out.writePair(obj->is<ArrayObject>() ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0);
}
bool
JSStructuredCloneWriter::traverseMap(HandleObject obj)
{
AutoValueVector newEntries(context());
if (!MapObject::entries(context(), obj, &newEntries))
return false;
for (size_t i = newEntries.length(); i > 0; --i) {
if (!entries.append(newEntries[i - 1]))
return false;
}
/* Push obj and count to the stack. */
if (!objs.append(ObjectValue(*obj)) || !counts.append(newEntries.length()))
return false;
checkStack();
/* Write the header for obj. */
return out.writePair(SCTAG_MAP_OBJECT, 0);
}
bool
JSStructuredCloneWriter::traverseSet(HandleObject obj)
{
AutoValueVector keys(context());
if (!SetObject::keys(context(), obj, &keys))
return false;
for (size_t i = keys.length(); i > 0; --i) {
if (!entries.append(keys[i - 1]))
return false;
}
/* Push obj and count to the stack. */
if (!objs.append(ObjectValue(*obj)) || !counts.append(keys.length()))
return false;
checkStack();
/* Write the header for obj. */
return out.writePair(SCTAG_SET_OBJECT, 0);
}
static bool
PrimitiveToObject(JSContext *cx, Value *vp)
{
@ -945,8 +992,10 @@ JSStructuredCloneWriter::startWrite(const Value &v)
if (v.isString()) {
return writeString(SCTAG_STRING, v.toString());
} else if (v.isNumber()) {
return out.writeDouble(v.toNumber());
} else if (v.isInt32()) {
return out.writePair(SCTAG_INT32, v.toInt32());
} else if (v.isDouble()) {
return out.writeDouble(v.toDouble());
} else if (v.isBoolean()) {
return out.writePair(SCTAG_BOOLEAN, v.toBoolean());
} else if (v.isNull()) {
@ -992,6 +1041,10 @@ JSStructuredCloneWriter::startWrite(const Value &v)
out.writeDouble(obj->as<NumberObject>().unbox());
} else if (obj->is<StringObject>()) {
return writeString(SCTAG_STRING_OBJECT, obj->as<StringObject>().unbox());
} else if (obj->is<MapObject>()) {
return traverseMap(obj);
} else if (obj->is<SetObject>()) {
return traverseSet(obj);
}
if (callbacks && callbacks->write)
@ -1116,10 +1169,27 @@ JSStructuredCloneWriter::write(const Value &v)
AutoCompartment ac(context(), obj);
if (counts.back()) {
counts.back()--;
RootedId id(context(), ids.back());
ids.popBack();
RootedValue key(context(), entries.back());
entries.popBack();
checkStack();
if (JSID_IS_STRING(id) || JSID_IS_INT(id)) {
if (obj->is<MapObject>()) {
counts.back()--;
RootedValue val(context(), entries.back());
entries.popBack();
checkStack();
if (!startWrite(key) || !startWrite(val))
return false;
} else if (obj->is<SetObject>()) {
if (!startWrite(key))
return false;
} else {
RootedId id(context());
if (!ValueToId<CanGC>(context(), key, &id))
return false;
MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id));
/*
* If obj still has an own property named id, write it out.
* The cost of re-checking could be avoided by using
@ -1131,14 +1201,16 @@ JSStructuredCloneWriter::write(const Value &v)
if (found) {
RootedValue val(context());
if (!writeId(id) ||
if (!startWrite(key) ||
!JSObject::getGeneric(context(), obj, obj, id, &val) ||
!startWrite(val))
{
return false;
}
}
}
} else {
out.writePair(SCTAG_NULL, 0);
out.writePair(SCTAG_END_OF_KEYS, 0);
objs.popBack();
counts.popBack();
}
@ -1359,6 +1431,10 @@ JSStructuredCloneReader::startRead(Value *vp)
vp->setUndefined();
break;
case SCTAG_INT32:
vp->setInt32(data);
break;
case SCTAG_BOOLEAN:
case SCTAG_BOOLEAN_OBJECT:
vp->setBoolean(!!data);
@ -1471,6 +1547,22 @@ JSStructuredCloneReader::startRead(Value *vp)
return false;
return readTypedArray(arrayType, data, vp);
case SCTAG_MAP_OBJECT: {
JSObject *obj = MapObject::create(context());
if (!obj || !objs.append(ObjectValue(*obj)))
return false;
vp->setObject(*obj);
break;
}
case SCTAG_SET_OBJECT: {
JSObject *obj = SetObject::create(context());
if (!obj || !objs.append(ObjectValue(*obj)))
return false;
vp->setObject(*obj);
break;
}
default: {
if (tag <= SCTAG_FLOAT_MAX) {
double d = ReinterpretPairAsDouble(tag, data);
@ -1504,36 +1596,6 @@ JSStructuredCloneReader::startRead(Value *vp)
return true;
}
bool
JSStructuredCloneReader::readId(jsid *idp)
{
uint32_t tag, data;
if (!in.readPair(&tag, &data))
return false;
if (tag == SCTAG_INDEX) {
*idp = INT_TO_JSID(int32_t(data));
return true;
}
if (tag == SCTAG_STRING) {
JSString *str = readString(data);
if (!str)
return false;
JSAtom *atom = AtomizeString(context(), str);
if (!atom)
return false;
*idp = NON_INTEGER_ATOM_TO_JSID(atom);
return true;
}
if (tag == SCTAG_NULL) {
*idp = JSID_VOID;
return true;
}
JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA, "id");
return false;
}
bool
JSStructuredCloneReader::readTransferMap()
{
@ -1590,7 +1652,7 @@ JSStructuredCloneReader::readTransferMap()
MOZ_ASSERT(obj);
MOZ_ASSERT(!cx->isExceptionPending());
}
// On failure, the buffer will still own the data (since its ownership will not get set to SCTAG_TMO_UNOWNED),
// so the data will be freed by ClearStructuredClone
if (!obj)
@ -1629,17 +1691,48 @@ JSStructuredCloneReader::read(Value *vp)
while (objs.length() != 0) {
RootedObject obj(context(), &objs.back().toObject());
RootedId id(context());
if (!readId(id.address()))
uint32_t tag, data;
if (!in.getPair(&tag, &data))
return false;
if (JSID_IS_VOID(id)) {
if (tag == SCTAG_END_OF_KEYS) {
MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
objs.popBack();
} else {
RootedValue v(context());
if (!startRead(v.address()) || !JSObject::defineGeneric(context(), obj, id, v))
return false;
continue;
}
RootedValue key(context());
if (!startRead(key.address()))
return false;
if (key.isNull() && !(obj->is<MapObject>() || obj->is<SetObject>())) {
// Backwards compatibility: Null used to indicate
// the end of object properties.
objs.popBack();
continue;
}
if (obj->is<SetObject>()) {
if (!SetObject::add(context(), obj, key))
return false;
continue;
}
RootedValue val(context());
if (!startRead(val.address()))
return false;
if (obj->is<MapObject>()) {
if (!MapObject::set(context(), obj, key, val))
return false;
} else {
RootedId id(context());
if (!ValueToId<CanGC>(context(), key, &id))
return false;
if (!JSObject::defineGeneric(context(), obj, id, val))
return false;
}
}
allObjs.clear();