Bug 1201620 - Make SavedFrame stacks structured cloneable; r=sfink

This commit is contained in:
Nick Fitzgerald 2015-10-12 13:29:48 -07:00
parent 365d409500
commit 130bed417f
9 changed files with 466 additions and 52 deletions

View File

@ -146,11 +146,11 @@ class VersionChangeTransaction;
// If JS_STRUCTURED_CLONE_VERSION changes then we need to update our major
// schema version.
static_assert(JS_STRUCTURED_CLONE_VERSION == 5,
static_assert(JS_STRUCTURED_CLONE_VERSION == 6,
"Need to update the major schema version.");
// Major schema version. Bump for almost everything.
const uint32_t kMajorSchemaVersion = 21;
const uint32_t kMajorSchemaVersion = 22;
// Minor schema version. Should almost always be 0 (maybe bump on release
// branches if we have to).
@ -4066,6 +4066,19 @@ UpgradeSchemaFrom20_0To21_0(mozIStorageConnection* aConnection)
return NS_OK;
}
nsresult
UpgradeSchemaFrom21_0To22_0(mozIStorageConnection* aConnection)
{
// The only change between 21 and 22 was a different structured clone format,
// but it's backwards-compatible.
nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(22, 0));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
nsresult
GetDatabaseFileURL(nsIFile* aDatabaseFile,
PersistenceType aPersistenceType,
@ -4558,7 +4571,7 @@ CreateStorageConnection(nsIFile* aDBFile,
}
} else {
// This logic needs to change next time we change the schema!
static_assert(kSQLiteSchemaVersion == int32_t((21 << 4) + 0),
static_assert(kSQLiteSchemaVersion == int32_t((22 << 4) + 0),
"Upgrade function needed due to schema version increase.");
while (schemaVersion != kSQLiteSchemaVersion) {
@ -4598,6 +4611,8 @@ CreateStorageConnection(nsIFile* aDBFile,
rv = UpgradeSchemaFrom19_0To20_0(aFMDirectory, connection);
} else if (schemaVersion == MakeSchemaVersion(20, 0)) {
rv = UpgradeSchemaFrom20_0To21_0(connection);
} else if (schemaVersion == MakeSchemaVersion(21, 0)) {
rv = UpgradeSchemaFrom21_0To22_0(connection);
} else {
IDB_WARNING("Unable to open IndexedDB database, no upgrade path is "
"available!");

View File

@ -121,7 +121,7 @@ typedef void (*FreeTransferStructuredCloneOp)(uint32_t tag, JS::TransferableOwne
// Increment this when anything at all changes in the serialization format.
// (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 5
#define JS_STRUCTURED_CLONE_VERSION 6
struct JSStructuredCloneCallbacks {
ReadStructuredCloneOp read;

View File

@ -78,3 +78,151 @@ BEGIN_TEST(testStructuredClone_string)
return true;
}
END_TEST(testStructuredClone_string)
struct StructuredCloneTestPrincipals final : public JSPrincipals {
uint32_t rank;
explicit StructuredCloneTestPrincipals(uint32_t rank, int32_t rc = 1) : rank(rank) {
this->refcount = rc;
}
bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
return JS_WriteUint32Pair(writer, rank, 0);
}
static bool read(JSContext* cx, JSStructuredCloneReader *reader, JSPrincipals** outPrincipals) {
uint32_t rank;
uint32_t unused;
if (!JS_ReadUint32Pair(reader, &rank, &unused))
return false;
*outPrincipals = new StructuredCloneTestPrincipals(rank);
return !!*outPrincipals;
}
static void destroy(JSPrincipals* p) {
auto p1 = static_cast<StructuredCloneTestPrincipals*>(p);
delete p1;
}
static uint32_t getRank(JSPrincipals* p) {
if (!p)
return 0;
return static_cast<StructuredCloneTestPrincipals*>(p)->rank;
}
static bool subsumes(JSPrincipals* a, JSPrincipals* b) {
return getRank(a) > getRank(b);
}
static JSSecurityCallbacks securityCallbacks;
static StructuredCloneTestPrincipals testPrincipals;
};
JSSecurityCallbacks StructuredCloneTestPrincipals::securityCallbacks = {
nullptr, // contentSecurityPolicyAllows
subsumes
};
BEGIN_TEST(testStructuredClone_SavedFrame)
{
JS_SetSecurityCallbacks(rt, &StructuredCloneTestPrincipals::securityCallbacks);
JS_InitDestroyPrincipalsCallback(rt, StructuredCloneTestPrincipals::destroy);
JS_InitReadPrincipalsCallback(rt, StructuredCloneTestPrincipals::read);
auto testPrincipals = new StructuredCloneTestPrincipals(42, 0);
CHECK(testPrincipals);
auto DONE = (JSPrincipals*) 0xDEADBEEF;
struct {
const char* name;
JSPrincipals* principals;
} principalsToTest[] = {
{ "IsSystem", &js::ReconstructedSavedFramePrincipals::IsSystem },
{ "IsNotSystem", &js::ReconstructedSavedFramePrincipals::IsNotSystem },
{ "testPrincipals", testPrincipals },
{ "nullptr principals", nullptr },
{ "DONE", DONE }
};
const char* FILENAME = "filename.js";
for (auto* pp = principalsToTest; pp->principals != DONE; pp++) {
fprintf(stderr, "Testing with principals '%s'\n", pp->name);
JS::RootedObject g(cx, JS_NewGlobalObject(cx, getGlobalClass(), pp->principals,
JS::FireOnNewGlobalHook));
CHECK(g);
JSAutoCompartment ac(cx, g);
CHECK(js::DefineTestingFunctions(cx, g, false, false));
JS::RootedValue srcVal(cx);
CHECK(evaluate("(function one() { \n" // 1
" return (function two() { \n" // 2
" return (function three() { \n" // 3
" return saveStack(); \n" // 4
" }()); \n" // 5
" }()); \n" // 6
"}()); \n", // 7
FILENAME,
1,
&srcVal));
CHECK(srcVal.isObject());
JS::RootedObject srcObj(cx, &srcVal.toObject());
CHECK(srcObj->is<js::SavedFrame>());
js::RootedSavedFrame srcFrame(cx, &srcObj->as<js::SavedFrame>());
CHECK(srcFrame->getPrincipals() == pp->principals);
JS::RootedValue destVal(cx);
CHECK(JS_StructuredClone(cx, srcVal, &destVal, nullptr, nullptr));
CHECK(destVal.isObject());
JS::RootedObject destObj(cx, &destVal.toObject());
CHECK(destObj->is<js::SavedFrame>());
auto destFrame = &destObj->as<js::SavedFrame>();
size_t framesCopied = 0;
for (auto& f : *destFrame) {
framesCopied++;
CHECK(&f != srcFrame);
if (pp->principals == testPrincipals) {
// We shouldn't get a pointer to the same
// StructuredCloneTestPrincipals instance since we should have
// serialized and then deserialized it into a new instance.
CHECK(f.getPrincipals() != pp->principals);
// But it should certainly have the same rank.
CHECK(StructuredCloneTestPrincipals::getRank(f.getPrincipals()) ==
StructuredCloneTestPrincipals::getRank(pp->principals));
} else {
// For our singleton principals, we should always get the same
// pointer back.
CHECK(js::ReconstructedSavedFramePrincipals::is(pp->principals) ||
pp->principals == nullptr);
CHECK(f.getPrincipals() == pp->principals);
}
CHECK(EqualStrings(f.getSource(), srcFrame->getSource()));
CHECK(f.getLine() == srcFrame->getLine());
CHECK(f.getColumn() == srcFrame->getColumn());
CHECK(EqualStrings(f.getFunctionDisplayName(), srcFrame->getFunctionDisplayName()));
srcFrame = srcFrame->getParent();
}
// Four function frames + one global frame.
CHECK(framesCopied == 4);
}
return true;
}
END_TEST(testStructuredClone_SavedFrame)

View File

@ -6438,4 +6438,3 @@ JS::ResetTimeZone()
icu::TimeZone::recreateDefault();
#endif
}

View File

@ -7,12 +7,15 @@
#ifndef vm_SavedFrame_h
#define vm_SavedFrame_h
#include "jswrapper.h"
#include "js/UbiNode.h"
namespace js {
class SavedFrame : public NativeObject {
friend class SavedStacks;
friend struct ::JSStructuredCloneReader;
public:
static const Class class_;
@ -120,6 +123,12 @@ class SavedFrame : public NativeObject {
!obj.as<SavedFrame>().getReservedSlot(JSSLOT_SOURCE).isNull();
}
static bool isSavedFrameOrWrapperAndNotProto(JSObject& obj) {
auto unwrapped = CheckedUnwrap(&obj);
MOZ_ASSERT(unwrapped);
return isSavedFrameAndNotProto(*unwrapped);
}
struct Lookup;
struct HashPolicy;
@ -142,8 +151,17 @@ class SavedFrame : public NativeObject {
};
private:
static SavedFrame* create(JSContext* cx);
static bool finishSavedFrameInit(JSContext* cx, HandleObject ctor, HandleObject proto);
void initFromLookup(HandleLookup lookup);
void initSource(JSAtom* source);
void initLine(uint32_t line);
void initColumn(uint32_t column);
void initFunctionDisplayName(JSAtom* maybeName);
void initAsyncCause(JSAtom* maybeCause);
void initParent(SavedFrame* maybeParent);
void initPrincipalsAlreadyHeld(JSPrincipals* principals);
void initPrincipals(JSPrincipals* principals);
enum {
// The reserved slots in the SavedFrame class.

View File

@ -22,13 +22,7 @@
inline void
js::AssertObjectIsSavedFrameOrWrapper(JSContext* cx, HandleObject stack)
{
#ifdef DEBUG
if (stack) {
RootedObject savedFrameObj(cx, CheckedUnwrap(stack));
MOZ_ASSERT(savedFrameObj);
MOZ_ASSERT(js::SavedFrame::isSavedFrameAndNotProto(*savedFrameObj));
}
#endif
MOZ_ASSERT_IF(stack, js::SavedFrame::isSavedFrameOrWrapperAndNotProto(*stack));
}
#endif // vm_SavedStacksInl_h

View File

@ -406,29 +406,91 @@ SavedFrame::getPrincipals()
return static_cast<JSPrincipals*>(v.toPrivate());
}
void
SavedFrame::initSource(JSAtom* source)
{
MOZ_ASSERT(source);
initReservedSlot(JSSLOT_SOURCE, StringValue(source));
}
void
SavedFrame::initLine(uint32_t line)
{
initReservedSlot(JSSLOT_LINE, PrivateUint32Value(line));
}
void
SavedFrame::initColumn(uint32_t column)
{
initReservedSlot(JSSLOT_COLUMN, PrivateUint32Value(column));
}
void
SavedFrame::initPrincipals(JSPrincipals* principals)
{
if (principals)
JS_HoldPrincipals(principals);
initPrincipalsAlreadyHeld(principals);
}
void
SavedFrame::initPrincipalsAlreadyHeld(JSPrincipals* principals)
{
MOZ_ASSERT_IF(principals, principals->refcount > 0);
initReservedSlot(JSSLOT_PRINCIPALS, PrivateValue(principals));
}
void
SavedFrame::initFunctionDisplayName(JSAtom* maybeName)
{
initReservedSlot(JSSLOT_FUNCTIONDISPLAYNAME, maybeName ? StringValue(maybeName) : NullValue());
}
void
SavedFrame::initAsyncCause(JSAtom* maybeCause)
{
initReservedSlot(JSSLOT_ASYNCCAUSE, maybeCause ? StringValue(maybeCause) : NullValue());
}
void
SavedFrame::initParent(SavedFrame* maybeParent)
{
initReservedSlot(JSSLOT_PARENT, ObjectOrNullValue(maybeParent));
}
void
SavedFrame::initFromLookup(SavedFrame::HandleLookup lookup)
{
MOZ_ASSERT(lookup->source);
MOZ_ASSERT(getReservedSlot(JSSLOT_SOURCE).isUndefined());
setReservedSlot(JSSLOT_SOURCE, StringValue(lookup->source));
initSource(lookup->source);
initLine(lookup->line);
initColumn(lookup->column);
initFunctionDisplayName(lookup->functionDisplayName);
initAsyncCause(lookup->asyncCause);
initParent(lookup->parent);
initPrincipals(lookup->principals);
}
setReservedSlot(JSSLOT_LINE, PrivateUint32Value(lookup->line));
setReservedSlot(JSSLOT_COLUMN, PrivateUint32Value(lookup->column));
setReservedSlot(JSSLOT_FUNCTIONDISPLAYNAME,
lookup->functionDisplayName
? StringValue(lookup->functionDisplayName)
: NullValue());
setReservedSlot(JSSLOT_ASYNCCAUSE,
lookup->asyncCause
? StringValue(lookup->asyncCause)
: NullValue());
setReservedSlot(JSSLOT_PARENT, ObjectOrNullValue(lookup->parent));
/* static */ SavedFrame*
SavedFrame::create(JSContext* cx)
{
RootedGlobalObject global(cx, cx->global());
assertSameCompartment(cx, global);
MOZ_ASSERT(getReservedSlot(JSSLOT_PRINCIPALS).isUndefined());
if (lookup->principals)
JS_HoldPrincipals(lookup->principals);
setReservedSlot(JSSLOT_PRINCIPALS, PrivateValue(lookup->principals));
// Ensure that we don't try to capture the stack again in the
// `SavedStacksMetadataCallback` for this new SavedFrame object, and
// accidentally cause O(n^2) behavior.
SavedStacks::AutoReentrancyGuard guard(cx->compartment()->savedStacks());
RootedNativeObject proto(cx, GlobalObject::getOrCreateSavedFramePrototype(cx, global));
if (!proto)
return nullptr;
assertSameCompartment(cx, proto);
RootedObject frameObj(cx, NewObjectWithGivenProto(cx, &SavedFrame::class_, proto));
if (!frameObj)
return nullptr;
return &frameObj->as<SavedFrame>();
}
bool
@ -1222,30 +1284,15 @@ SavedStacks::getOrCreateSavedFrame(JSContext* cx, SavedFrame::HandleLookup looku
SavedFrame*
SavedStacks::createFrameFromLookup(JSContext* cx, SavedFrame::HandleLookup lookup)
{
RootedGlobalObject global(cx, cx->global());
assertSameCompartment(cx, global);
// Ensure that we don't try to capture the stack again in the
// `SavedStacksMetadataCallback` for this new SavedFrame object, and
// accidentally cause O(n^2) behavior.
SavedStacks::AutoReentrancyGuard guard(*this);
RootedNativeObject proto(cx, GlobalObject::getOrCreateSavedFramePrototype(cx, global));
if (!proto)
RootedSavedFrame frame(cx, SavedFrame::create(cx));
if (!frame)
return nullptr;
assertSameCompartment(cx, proto);
frame->initFromLookup(lookup);
RootedObject frameObj(cx, NewObjectWithGivenProto(cx, &SavedFrame::class_, proto));
if (!frameObj)
if (!FreezeObject(cx, frame))
return nullptr;
RootedSavedFrame f(cx, &frameObj->as<SavedFrame>());
f->initFromLookup(lookup);
if (!FreezeObject(cx, frameObj))
return nullptr;
return f.get();
return frame;
}
/*

View File

@ -148,6 +148,7 @@ namespace js {
// principals.
class SavedStacks {
friend class SavedFrame;
friend JSObject* SavedStacksMetadataCallback(JSContext* cx, JSObject* target);
friend bool JS::ubi::ConstructSavedFrameStackSlow(JSContext* cx,
JS::ubi::StackFrame& ubiFrame,

View File

@ -42,6 +42,7 @@
#include "builtin/MapObject.h"
#include "js/Date.h"
#include "js/TraceableHashTable.h"
#include "vm/SavedFrame.h"
#include "vm/SharedArrayObject.h"
#include "vm/TypedArrayObject.h"
#include "vm/WrapperObject.h"
@ -92,6 +93,12 @@ enum StructuredDataType : uint32_t {
SCTAG_END_OF_KEYS,
SCTAG_SHARED_TYPED_ARRAY_OBJECT,
SCTAG_DATA_VIEW_OBJECT,
SCTAG_SAVED_FRAME_OBJECT,
SCTAG_JSPRINCIPALS,
SCTAG_NULL_JSPRINCIPALS,
SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM,
SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM,
SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100,
SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int8,
@ -243,6 +250,7 @@ struct JSStructuredCloneReader {
bool readSharedTypedArray(uint32_t arrayType, uint32_t nelems, MutableHandleValue vp);
bool readArrayBuffer(uint32_t nbytes, MutableHandleValue vp);
bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, MutableHandleValue vp);
JSObject* readSavedFrame(uint32_t principalsTag);
bool startRead(MutableHandleValue vp);
SCInput& in;
@ -305,6 +313,7 @@ struct JSStructuredCloneWriter {
bool traverseObject(HandleObject obj);
bool traverseMap(HandleObject obj);
bool traverseSet(HandleObject obj);
bool traverseSavedFrame(HandleObject obj);
bool parseTransferable();
bool reportErrorTransferable(uint32_t errorId);
@ -327,6 +336,7 @@ struct JSStructuredCloneWriter {
// For JSObject: Property IDs as value
// For Map: Key followed by value
// For Set: Key
// For SavedFrame: parent SavedFrame
AutoValueVector entries;
// The "memory" list described in the HTML5 internal structured cloning algorithm.
@ -1056,6 +1066,84 @@ JSStructuredCloneWriter::traverseSet(HandleObject obj)
//
// Notice how the end-of-children marker for key1 is sandwiched between the
// value1 beginning and end.
bool
JSStructuredCloneWriter::traverseSavedFrame(HandleObject obj)
{
RootedObject unwrapped(context(), js::CheckedUnwrap(obj));
MOZ_ASSERT(unwrapped && unwrapped->is<SavedFrame>());
RootedSavedFrame savedFrame(context(), &unwrapped->as<SavedFrame>());
RootedObject parent(context(), savedFrame->getParent());
if (!context()->compartment()->wrap(context(), &parent))
return false;
if (!objs.append(ObjectValue(*obj)) ||
!entries.append(parent ? ObjectValue(*parent) : NullValue()) ||
!counts.append(1))
{
return false;
}
checkStack();
// Write the SavedFrame tag and the SavedFrame's principals.
if (savedFrame->getPrincipals() == &ReconstructedSavedFramePrincipals::IsSystem) {
if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT,
SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM))
{
return false;
};
} else if (savedFrame->getPrincipals() == &ReconstructedSavedFramePrincipals::IsNotSystem) {
if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT,
SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM))
{
return false;
}
} else {
if (auto principals = savedFrame->getPrincipals()) {
if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_JSPRINCIPALS) ||
!principals->write(context(), this))
{
return false;
}
} else {
if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_NULL_JSPRINCIPALS))
return false;
}
}
// Write the SavedFrame's reserved slots, except for the parent, which is
// queued on objs for further traversal.
RootedValue val(context());
val = StringValue(savedFrame->getSource());
if (!startWrite(val))
return false;
val = NumberValue(savedFrame->getLine());
if (!startWrite(val))
return false;
val = NumberValue(savedFrame->getColumn());
if (!startWrite(val))
return false;
auto name = savedFrame->getFunctionDisplayName();
val = name ? StringValue(name) : NullValue();
if (!startWrite(val))
return false;
auto cause = savedFrame->getAsyncCause();
val = cause ? StringValue(cause) : NullValue();
if (!startWrite(val))
return false;
return true;
}
bool
JSStructuredCloneWriter::startWrite(HandleValue v)
{
@ -1130,6 +1218,8 @@ JSStructuredCloneWriter::startWrite(HandleValue v)
return traverseMap(obj);
} else if (cls == ESClass_Set) {
return traverseSet(obj);
} else if (SavedFrame::isSavedFrameOrWrapperAndNotProto(*obj)) {
return traverseSavedFrame(obj);
}
if (callbacks && callbacks->write)
@ -1287,7 +1377,7 @@ JSStructuredCloneWriter::write(HandleValue v)
if (!startWrite(key) || !startWrite(val))
return false;
} else if (cls == ESClass_Set) {
} else if (cls == ESClass_Set || SavedFrame::isSavedFrameOrWrapperAndNotProto(*obj)) {
if (!startWrite(key))
return false;
} else {
@ -1796,6 +1886,14 @@ JSStructuredCloneReader::startRead(MutableHandleValue vp)
break;
}
case SCTAG_SAVED_FRAME_OBJECT: {
auto obj = readSavedFrame(data);
if (!obj || !objs.append(ObjectValue(*obj)))
return false;
vp.setObject(*obj);
break;
}
default: {
if (tag <= SCTAG_FLOAT_MAX) {
double d = ReinterpretPairAsDouble(tag, data);
@ -1913,6 +2011,83 @@ JSStructuredCloneReader::readTransferMap()
return true;
}
JSObject*
JSStructuredCloneReader::readSavedFrame(uint32_t principalsTag)
{
RootedSavedFrame savedFrame(context(), SavedFrame::create(context()));
if (!savedFrame)
return nullptr;
JSPrincipals* principals;
if (principalsTag == SCTAG_JSPRINCIPALS) {
if (!context()->runtime()->readPrincipals) {
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
JSMSG_SC_UNSUPPORTED_TYPE);
return nullptr;
}
if (!context()->runtime()->readPrincipals(context(), this, &principals))
return nullptr;
} else if (principalsTag == SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM) {
principals = &ReconstructedSavedFramePrincipals::IsSystem;
principals->refcount++;
} else if (principalsTag == SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM) {
principals = &ReconstructedSavedFramePrincipals::IsNotSystem;
principals->refcount++;
} else if (principalsTag == SCTAG_NULL_JSPRINCIPALS) {
principals = nullptr;
} else {
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA, "bad SavedFrame principals");
return nullptr;
}
savedFrame->initPrincipalsAlreadyHeld(principals);
RootedValue source(context());
if (!startRead(&source) || !source.isString())
return nullptr;
auto atomSource = AtomizeString(context(), source.toString());
if (!atomSource)
return nullptr;
savedFrame->initSource(atomSource);
RootedValue lineVal(context());
uint32_t line;
if (!startRead(&lineVal) || !lineVal.isNumber() || !ToUint32(context(), lineVal, &line))
return nullptr;
savedFrame->initLine(line);
RootedValue columnVal(context());
uint32_t column;
if (!startRead(&columnVal) || !columnVal.isNumber() || !ToUint32(context(), columnVal, &column))
return nullptr;
savedFrame->initColumn(column);
RootedValue name(context());
if (!startRead(&name) || !(name.isString() || name.isNull()))
return nullptr;
JSAtom* atomName = nullptr;
if (name.isString()) {
atomName = AtomizeString(context(), name.toString());
if (!atomName)
return nullptr;
}
savedFrame->initFunctionDisplayName(atomName);
RootedValue cause(context());
if (!startRead(&cause) || !(cause.isString() || cause.isNull()))
return nullptr;
JSAtom* atomCause = nullptr;
if (cause.isString()) {
atomCause = AtomizeString(context(), cause.toString());
if (!atomCause)
return nullptr;
}
savedFrame->initAsyncCause(atomCause);
return savedFrame;
}
// Perform the whole recursive reading procedure.
bool
JSStructuredCloneReader::read(MutableHandleValue vp)
@ -1961,7 +2136,9 @@ JSStructuredCloneReader::read(MutableHandleValue vp)
if (!startRead(&key))
return false;
if (key.isNull() && !(obj->is<MapObject>() || obj->is<SetObject>())) {
if (key.isNull() &&
!(obj->is<MapObject>() || obj->is<SetObject>() || obj->is<SavedFrame>()))
{
// Backwards compatibility: Null formerly indicated the end of
// object properties.
objs.popBack();
@ -1976,6 +2153,21 @@ JSStructuredCloneReader::read(MutableHandleValue vp)
continue;
}
// SavedFrame object: there is one following value, the parent
// SavedFrame, which is either null or another SavedFrame object.
if (obj->is<SavedFrame>()) {
SavedFrame* parentFrame;
if (key.isNull())
parentFrame = nullptr;
else if (key.isObject() && key.toObject().is<SavedFrame>())
parentFrame = &key.toObject().as<SavedFrame>();
else
return false;
obj->as<SavedFrame>().initParent(parentFrame);
continue;
}
// Everything else uses a series of key,value,key,value,... Value
// objects.
RootedValue val(context());