mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-13 19:41:49 +00:00
Bug 1036136 - Implement structured cloning for Map and Set objects. r=jorendorff,bent
This commit is contained in:
parent
3274c04222
commit
c25e1b9944
@ -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[];
|
||||
|
110
js/src/jit-test/tests/structured-clone/Map.js
Normal file
110
js/src/jit-test/tests/structured-clone/Map.js
Normal file
@ -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");
|
82
js/src/jit-test/tests/structured-clone/Set.js
Normal file
82
js/src/jit-test/tests/structured-clone/Set.js
Normal file
@ -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");
|
13
js/src/jit-test/tests/structured-clone/version3.js
Normal file
13
js/src/jit-test/tests/structured-clone/version3.js
Normal file
@ -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 ∈
|
||||
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user