It should be possible to post (clone) built-in JS objects to Workers
https://bugs.webkit.org/show_bug.cgi?id=22878

Reviewed by Gavin Barraclough.

Implement object cloning semantics for postMessage.  Currently only
a partial implementation of the spec -- cloning of File, FileList,
ImageData, and RegExp were left out as they would have significantly
increased patch size.

Cloning requires multiple tree walks so we use a templated tree
walk function, allowing us to share a single implementation for
serialization, deserialization, and eventual destruction of the
serialized object tree.

Test: fast/dom/Window/window-postmessage-clone.html

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@49214 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/WebCore/bindings/js/JSDOMWindowCustom.cpp b/WebCore/bindings/js/JSDOMWindowCustom.cpp
index 6a66a4d2..47339d4 100644
--- a/WebCore/bindings/js/JSDOMWindowCustom.cpp
+++ b/WebCore/bindings/js/JSDOMWindowCustom.cpp
@@ -79,6 +79,7 @@
 #include "RegisteredEventListener.h"
 #include "ScheduledAction.h"
 #include "ScriptController.h"
+#include "SerializedScriptValue.h"
 #include "Settings.h"
 #include "SharedWorkerRepository.h"
 #include "WindowFeatures.h"
@@ -952,7 +953,7 @@
     DOMWindow* window = impl();
 
     DOMWindow* source = asJSDOMWindow(exec->lexicalGlobalObject())->impl();
-    String message = args.at(0).toString(exec);
+    PassRefPtr<SerializedScriptValue> message = SerializedScriptValue::create(exec, args.at(0));
 
     if (exec->hadException())
         return jsUndefined();
diff --git a/WebCore/bindings/js/JSMessageEventCustom.cpp b/WebCore/bindings/js/JSMessageEventCustom.cpp
index 4c61c04..2e7b2d06 100644
--- a/WebCore/bindings/js/JSMessageEventCustom.cpp
+++ b/WebCore/bindings/js/JSMessageEventCustom.cpp
@@ -59,7 +59,7 @@
     const UString& typeArg = args.at(0).toString(exec);
     bool canBubbleArg = args.at(1).toBoolean(exec);
     bool cancelableArg = args.at(2).toBoolean(exec);
-    const UString& dataArg = args.at(3).toString(exec);
+    PassRefPtr<SerializedScriptValue> dataArg = SerializedScriptValue::create(exec, args.at(3));
     const UString& originArg = args.at(4).toString(exec);
     const UString& lastEventIdArg = args.at(5).toString(exec);
     DOMWindow* sourceArg = toDOMWindow(args.at(6));
diff --git a/WebCore/bindings/js/JSMessagePortCustom.h b/WebCore/bindings/js/JSMessagePortCustom.h
index 7e90943..17b1eae 100644
--- a/WebCore/bindings/js/JSMessagePortCustom.h
+++ b/WebCore/bindings/js/JSMessagePortCustom.h
@@ -49,7 +49,7 @@
     template <typename T>
     inline JSC::JSValue handlePostMessage(JSC::ExecState* exec, const JSC::ArgList& args, T* impl)
     {
-        String message = args.at(0).toString(exec);
+        PassRefPtr<SerializedScriptValue> message = SerializedScriptValue::create(exec, args.at(0));
         MessagePortArray portArray;
         fillMessagePortArray(exec, args.at(1), portArray);
         if (exec->hadException())
diff --git a/WebCore/bindings/js/SerializedScriptValue.cpp b/WebCore/bindings/js/SerializedScriptValue.cpp
new file mode 100644
index 0000000..ab97f2f
--- /dev/null
+++ b/WebCore/bindings/js/SerializedScriptValue.cpp
@@ -0,0 +1,833 @@
+/*
+ * Copyright (C) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "config.h"
+
+#include "SerializedScriptValue.h"
+
+#include <runtime/DateInstance.h>
+#include <runtime/ExceptionHelpers.h>
+#include <runtime/PropertyNameArray.h>
+#include <wtf/HashTraits.h>
+#include <wtf/Vector.h>
+
+using namespace JSC;
+
+namespace WebCore
+{
+
+class SerializedObject : public SharedSerializedData
+{
+public:
+    typedef Vector<RefPtr<StringImpl> > PropertyNameList;
+    typedef Vector<SerializedScriptValueData> ValueList;
+    void set(const Identifier& propertyName, const SerializedScriptValueData& value)
+    {
+        ASSERT(m_names.size() == m_values.size());
+        m_names.append(String(propertyName.ustring()).crossThreadString().impl());
+        m_values.append(value);
+    }
+    PropertyNameList& names() { return m_names; }
+    ValueList& values() { return m_values; }
+    static PassRefPtr<SerializedObject> create()
+    {
+        return adoptRef(new SerializedObject);
+    }
+    void clear()
+    {
+        m_names.clear();
+        m_values.clear();
+    }
+private:
+    SerializedObject() { }
+    PropertyNameList m_names;
+    ValueList m_values;
+};
+
+class SerializedArray : public SharedSerializedData
+{
+    typedef HashMap<unsigned, SerializedScriptValueData, DefaultHash<unsigned>::Hash, WTF::UnsignedWithZeroKeyHashTraits<unsigned> > SparseMap;
+public:
+    void setIndex(unsigned index, const SerializedScriptValueData& value)
+    {
+        ASSERT(index < m_length);
+        if (index == m_compactStorage.size())
+            m_compactStorage.append(value);
+        else
+            m_sparseStorage.set(index, value);
+    }
+
+    bool canDoFastRead(unsigned index) const
+    {
+        ASSERT(index < m_length);
+        return index < m_compactStorage.size();
+    }
+
+    const SerializedScriptValueData& getIndex(unsigned index)
+    {
+        ASSERT(index < m_compactStorage.size());
+        return m_compactStorage[index];
+    }
+
+    SerializedScriptValueData getSparseIndex(unsigned index, bool& hasIndex)
+    {
+        ASSERT(index >= m_compactStorage.size());
+        ASSERT(index < m_length);
+        SparseMap::iterator iter = m_sparseStorage.find(index);
+        if (iter == m_sparseStorage.end()) {
+            hasIndex = false;
+            return SerializedScriptValueData();
+        }
+        hasIndex = true;
+        return iter->second;
+    }
+
+    unsigned length() const
+    {
+        return m_length;
+    }
+
+    static PassRefPtr<SerializedArray> create(unsigned length)
+    {
+        return adoptRef(new SerializedArray(length));
+    }
+
+    void clear()
+    {
+        m_compactStorage.clear();
+        m_sparseStorage.clear();
+        m_length = 0;
+    }
+private:
+    SerializedArray(unsigned length)
+        : m_length(length)
+    {
+    }
+
+    Vector<SerializedScriptValueData> m_compactStorage;
+    SparseMap m_sparseStorage;
+    unsigned m_length;
+};
+
+SerializedScriptValueData::SerializedScriptValueData(RefPtr<SerializedObject> data)
+    : m_type(ObjectType)
+    , m_sharedData(data)
+{
+}
+
+SerializedScriptValueData::SerializedScriptValueData(RefPtr<SerializedArray> data)
+    : m_type(ArrayType)
+    , m_sharedData(data)
+{
+}
+
+SerializedArray* SharedSerializedData::asArray()
+{
+    return static_cast<SerializedArray*>(this);
+}
+
+SerializedObject* SharedSerializedData::asObject()
+{
+    return static_cast<SerializedObject*>(this);
+}
+
+static const unsigned maximumFilterRecursion = 40000;
+enum WalkerState { StateUnknown, ArrayStartState, ArrayStartVisitMember, ArrayEndVisitMember,
+    ObjectStartState, ObjectStartVisitMember, ObjectEndVisitMember };
+template <typename TreeWalker> typename TreeWalker::OutputType walk(TreeWalker& context, typename TreeWalker::InputType in)
+{
+    typedef typename TreeWalker::InputObject InputObject;
+    typedef typename TreeWalker::InputArray InputArray;
+    typedef typename TreeWalker::OutputObject OutputObject;
+    typedef typename TreeWalker::OutputArray OutputArray;
+    typedef typename TreeWalker::InputType InputType;
+    typedef typename TreeWalker::OutputType OutputType;
+    typedef typename TreeWalker::PropertyList PropertyList;
+
+    Vector<uint32_t, 16> indexStack;
+    Vector<uint32_t, 16> lengthStack;
+    Vector<PropertyList, 16> propertyStack;
+    Vector<InputObject, 16> inputObjectStack;
+    Vector<InputArray, 16> inputArrayStack;
+    Vector<OutputObject, 16> outputObjectStack;
+    Vector<OutputArray, 16> outputArrayStack;
+    Vector<WalkerState, 16> stateStack;
+    WalkerState state = StateUnknown;
+    InputType inValue = in;
+    OutputType outValue = context.null();
+
+    unsigned tickCount = context.ticksUntilNextCheck();
+    while (1) {
+        switch (state) {
+            arrayStartState:
+            case ArrayStartState: {
+                ASSERT(context.isArray(inValue));
+                if (inputObjectStack.size() + inputArrayStack.size() > maximumFilterRecursion) {
+                    context.throwStackOverflow();
+                    return context.null();
+                }
+
+                InputArray inArray = context.asInputArray(inValue);
+                unsigned length = context.length(inArray);
+                OutputArray outArray = context.createOutputArray(length);
+                if (!context.startArray(inArray, outArray))
+                    return context.null();
+                inputArrayStack.append(inArray);
+                outputArrayStack.append(outArray);
+                indexStack.append(0);
+                lengthStack.append(length);
+                // fallthrough
+            }
+            arrayStartVisitMember:
+            case ArrayStartVisitMember: {
+                if (!--tickCount) {
+                    if (context.didTimeOut()) {
+                        context.throwInterruptedException();
+                        return context.null();
+                    }
+                    tickCount = context.ticksUntilNextCheck();
+                }
+
+                InputArray array = inputArrayStack.last();
+                uint32_t index = indexStack.last();
+                if (index == lengthStack.last()) {
+                    InputArray inArray = inputArrayStack.last();
+                    OutputArray outArray = outputArrayStack.last();
+                    context.endArray(inArray, outArray);
+                    outValue = outArray;
+                    inputArrayStack.removeLast();
+                    outputArrayStack.removeLast();
+                    indexStack.removeLast();
+                    lengthStack.removeLast();
+                    break;
+                }
+                if (context.canDoFastRead(array, index))
+                    inValue = context.getIndex(array, index);
+                else {
+                    bool hasIndex = false;
+                    inValue = context.getSparseIndex(array, index, hasIndex);
+                    if (!hasIndex) {
+                        indexStack.last()++;
+                        goto arrayStartVisitMember;
+                    }
+                }
+
+                if (OutputType transformed = context.convertIfTerminal(inValue))
+                    outValue = transformed;
+                else {
+                    stateStack.append(ArrayEndVisitMember);
+                    goto stateUnknown;
+                }
+                // fallthrough
+            }
+            case ArrayEndVisitMember: {
+                OutputArray outArray = outputArrayStack.last();
+                context.putProperty(outArray, indexStack.last(), outValue);
+                indexStack.last()++;
+                goto arrayStartVisitMember;
+            }
+            objectStartState:
+            case ObjectStartState: {
+                ASSERT(context.isObject(inValue));
+                if (inputObjectStack.size() + inputArrayStack.size() > maximumFilterRecursion) {
+                    context.throwStackOverflow();
+                    return context.null();
+                }
+                InputObject inObject = context.asInputObject(inValue);
+                OutputObject outObject = context.createOutputObject();
+                if (!context.startObject(inObject, outObject))
+                    return context.null();
+                inputObjectStack.append(inObject);
+                outputObjectStack.append(outObject);
+                indexStack.append(0);
+                context.getPropertyNames(inObject, propertyStack);
+                // fallthrough
+            }
+            objectStartVisitMember:
+            case ObjectStartVisitMember: {
+                if (!--tickCount) {
+                    if (context.didTimeOut()) {
+                        context.throwInterruptedException();
+                        return context.null();
+                    }
+                    tickCount = context.ticksUntilNextCheck();
+                }
+
+                InputObject object = inputObjectStack.last();
+                uint32_t index = indexStack.last();
+                PropertyList& properties = propertyStack.last();
+                if (index == properties.size()) {
+                    InputObject inObject = inputObjectStack.last();
+                    OutputObject outObject = outputObjectStack.last();
+                    context.endObject(inObject, outObject);
+                    outValue = outObject;
+                    inputObjectStack.removeLast();
+                    outputObjectStack.removeLast();
+                    indexStack.removeLast();
+                    propertyStack.removeLast();
+                    break;
+                }
+                inValue = context.getProperty(object, properties[index], index);
+
+                if (context.shouldTerminate())
+                    return context.null();
+
+                if (OutputType transformed = context.convertIfTerminal(inValue))
+                    outValue = transformed;
+                else {
+                    stateStack.append(ObjectEndVisitMember);
+                    goto stateUnknown;
+                }
+                // fallthrough
+            }
+            case ObjectEndVisitMember: {
+                context.putProperty(outputObjectStack.last(), propertyStack.last()[indexStack.last()], outValue);
+                if (context.shouldTerminate())
+                    return context.null();
+
+                indexStack.last()++;
+                goto objectStartVisitMember;
+            }
+            stateUnknown:
+            case StateUnknown:
+                if (OutputType transformed = context.convertIfTerminal(inValue)) {
+                    outValue = transformed;
+                    break;
+                }
+                if (context.isArray(inValue))
+                    goto arrayStartState;
+                goto objectStartState;
+        }
+        if (stateStack.isEmpty())
+            break;
+
+        state = stateStack.last();
+        stateStack.removeLast();
+
+        if (!--tickCount) {
+            if (context.didTimeOut()) {
+                context.throwInterruptedException();
+                return context.null();
+            }
+            tickCount = context.ticksUntilNextCheck();
+        }
+    }
+    return outValue;
+}
+
+struct BaseWalker {
+    BaseWalker(ExecState* exec)
+        : m_exec(exec)
+        , m_timeoutChecker(exec->globalData().timeoutChecker)
+    {
+        m_timeoutChecker.reset();
+    }
+    ExecState* m_exec;
+    TimeoutChecker m_timeoutChecker;
+    MarkedArgumentBuffer m_gcBuffer;
+
+    bool shouldTerminate()
+    {
+        return m_exec->hadException();
+    }
+
+    unsigned ticksUntilNextCheck()
+    {
+        return m_timeoutChecker.ticksUntilNextCheck();
+    }
+
+    bool didTimeOut()
+    {
+        return m_timeoutChecker.didTimeOut(m_exec);
+    }
+
+    void throwStackOverflow()
+    {
+        m_exec->setException(createStackOverflowError(m_exec));
+    }
+
+    void throwInterruptedException()
+    {
+        m_exec->setException(createInterruptedExecutionException(&m_exec->globalData()));
+    }
+};
+
+struct SerializingTreeWalker : public BaseWalker {
+    typedef JSValue InputType;
+    typedef JSArray* InputArray;
+    typedef JSObject* InputObject;
+    typedef SerializedScriptValueData OutputType;
+    typedef RefPtr<SerializedArray> OutputArray;
+    typedef RefPtr<SerializedObject> OutputObject;
+    typedef PropertyNameArray PropertyList;
+
+    SerializingTreeWalker(ExecState* exec)
+        : BaseWalker(exec)
+    {
+    }
+
+    OutputType null() { return SerializedScriptValueData(); }
+
+    bool isArray(JSValue value)
+    {
+        if (!value.isObject())
+            return false;
+        JSObject* object = asObject(value);
+        return isJSArray(&m_exec->globalData(), object) || object->inherits(&JSArray::info);
+    }
+
+    bool isObject(JSValue value)
+    {
+        return value.isObject();
+    }
+
+    JSArray* asInputArray(JSValue value)
+    {
+        return asArray(value);
+    }
+
+    JSObject* asInputObject(JSValue value)
+    {
+        return asObject(value);
+    }
+
+    PassRefPtr<SerializedArray> createOutputArray(unsigned length)
+    {
+        return SerializedArray::create(length);
+    }
+
+    PassRefPtr<SerializedObject> createOutputObject()
+    {
+        return SerializedObject::create();
+    }
+
+    uint32_t length(JSValue array)
+    {
+        ASSERT(array.isObject());
+        JSObject* object = asObject(array);
+        return object->get(m_exec, m_exec->propertyNames().length).toUInt32(m_exec);
+    }
+
+    bool canDoFastRead(JSArray* array, unsigned index)
+    {
+        return isJSArray(&m_exec->globalData(), array) && array->canGetIndex(index);
+    }
+
+    JSValue getIndex(JSArray* array, unsigned index)
+    {
+        return array->getIndex(index);
+    }
+
+    JSValue getSparseIndex(JSObject* object, unsigned propertyName, bool& hasIndex)
+    {
+        PropertySlot slot(object);
+        if (object->getOwnPropertySlot(m_exec, propertyName, slot)) {
+            hasIndex = true;
+            return slot.getValue(m_exec, propertyName);
+        }
+        hasIndex = false;
+        return jsNull();
+    }
+
+    JSValue getProperty(JSObject* object, const Identifier& propertyName, unsigned)
+    {
+        PropertySlot slot(object);
+        if (object->getOwnPropertySlot(m_exec, propertyName, slot))
+            return slot.getValue(m_exec, propertyName);
+        return jsNull();
+    }
+
+    SerializedScriptValueData convertIfTerminal(JSValue value)
+    {
+        if (!value.isCell())
+            return SerializedScriptValueData(value);
+
+        if (value.isString())
+            return SerializedScriptValueData(asString(value)->value());
+
+        if (value.isNumber())
+            return SerializedScriptValueData(SerializedScriptValueData::NumberType, value.uncheckedGetNumber());
+
+        if (value.isObject() && asObject(value)->inherits(&DateInstance::info))
+            return SerializedScriptValueData(SerializedScriptValueData::DateType, asDateInstance(value)->internalNumber());
+
+        if (isArray(value))
+            return SerializedScriptValueData();
+
+        CallData unusedData;
+        if (value.isObject() && value.getCallData(unusedData) == CallTypeNone)
+            return SerializedScriptValueData();
+
+        // Any other types are expected to serialize as null.
+        return SerializedScriptValueData(jsNull());
+    }
+
+    void getPropertyNames(JSObject* object, Vector<PropertyNameArray, 16>& propertyStack)
+    {
+        propertyStack.append(PropertyNameArray(m_exec));
+        object->getOwnPropertyNames(m_exec, propertyStack.last());
+    }
+
+    void putProperty(RefPtr<SerializedArray> array, unsigned propertyName, const SerializedScriptValueData& value)
+    {
+        array->setIndex(propertyName, value);
+    }
+
+    void putProperty(RefPtr<SerializedObject> object, const Identifier& propertyName, const SerializedScriptValueData& value)
+    {
+        object->set(propertyName, value);
+    }
+
+    bool startArray(JSArray* inArray, RefPtr<SerializedArray>)
+    {
+        // Cycle detection
+        if (!m_cycleDetector.add(inArray).second) {
+            m_exec->setException(createTypeError(m_exec, "Cannot post cyclic structures."));
+            return false;
+        }
+        m_gcBuffer.append(inArray);
+        return true;
+    }
+
+    void endArray(JSArray* inArray, RefPtr<SerializedArray>)
+    {
+        m_cycleDetector.remove(inArray);
+        m_gcBuffer.removeLast();
+    }
+
+    bool startObject(JSObject* inObject, RefPtr<SerializedObject>)
+    {
+        // Cycle detection
+        if (!m_cycleDetector.add(inObject).second) {
+            m_exec->setException(createTypeError(m_exec, "Cannot post cyclic structures."));
+            return false;
+        }
+        m_gcBuffer.append(inObject);
+        return true;
+    }
+
+    void endObject(JSObject* inObject, RefPtr<SerializedObject>)
+    {
+        m_cycleDetector.remove(inObject);
+        m_gcBuffer.removeLast();
+    }
+
+private:
+    HashSet<JSObject*> m_cycleDetector;
+};
+
+SerializedScriptValueData SerializedScriptValueData::serialize(ExecState* exec, JSValue inValue)
+{
+    SerializingTreeWalker context(exec);
+    return walk<SerializingTreeWalker>(context, inValue);
+}
+
+
+struct DeserializingTreeWalker : public BaseWalker {
+    typedef SerializedScriptValueData InputType;
+    typedef RefPtr<SerializedArray> InputArray;
+    typedef RefPtr<SerializedObject> InputObject;
+    typedef JSValue OutputType;
+    typedef JSArray* OutputArray;
+    typedef JSObject* OutputObject;
+    typedef SerializedObject::PropertyNameList PropertyList;
+
+    DeserializingTreeWalker(ExecState* exec, bool mustCopy)
+        : BaseWalker(exec)
+        , m_mustCopy(mustCopy)
+    {
+    }
+
+    OutputType null() { return jsNull(); }
+
+    bool isArray(const SerializedScriptValueData& value)
+    {
+        return value.type() == SerializedScriptValueData::ArrayType;
+    }
+
+    bool isObject(const SerializedScriptValueData& value)
+    {
+        return value.type() == SerializedScriptValueData::ObjectType;
+    }
+
+    SerializedArray* asInputArray(const SerializedScriptValueData& value)
+    {
+        return value.asArray();
+    }
+
+    SerializedObject* asInputObject(const SerializedScriptValueData& value)
+    {
+        return value.asObject();
+    }
+
+    JSArray* createOutputArray(unsigned length)
+    {
+        JSArray* array = constructEmptyArray(m_exec);
+        array->setLength(length);
+        return array;
+    }
+
+    JSObject* createOutputObject()
+    {
+        return constructEmptyObject(m_exec);
+    }
+
+    uint32_t length(RefPtr<SerializedArray> array)
+    {
+        return array->length();
+    }
+
+    bool canDoFastRead(RefPtr<SerializedArray> array, unsigned index)
+    {
+        return array->canDoFastRead(index);
+    }
+
+    SerializedScriptValueData getIndex(RefPtr<SerializedArray> array, unsigned index)
+    {
+        return array->getIndex(index);
+    }
+
+    SerializedScriptValueData getSparseIndex(RefPtr<SerializedArray> array, unsigned propertyName, bool& hasIndex)
+    {
+        return array->getSparseIndex(propertyName, hasIndex);
+    }
+
+    SerializedScriptValueData getProperty(RefPtr<SerializedObject> object, const RefPtr<StringImpl>& propertyName, unsigned propertyIndex)
+    {
+        ASSERT(object->names()[propertyIndex] == propertyName);
+        return object->values()[propertyIndex];
+    }
+
+    JSValue convertIfTerminal(SerializedScriptValueData& value)
+    {
+        switch (value.type()) {
+            case SerializedScriptValueData::ArrayType:
+            case SerializedScriptValueData::ObjectType:
+                return JSValue();
+            case SerializedScriptValueData::StringType:
+                return jsString(m_exec, value.asString().crossThreadString());
+            case SerializedScriptValueData::ImmediateType:
+                return value.asImmediate();
+            case SerializedScriptValueData::NumberType:
+                return jsNumber(m_exec, value.asDouble());
+            case SerializedScriptValueData::DateType:
+                return new (m_exec) DateInstance(m_exec, value.asDouble());
+            default:
+                ASSERT_NOT_REACHED();
+                return JSValue();
+        }
+    }
+
+    void getPropertyNames(RefPtr<SerializedObject> object, Vector<SerializedObject::PropertyNameList, 16>& properties)
+    {
+        properties.append(object->names());
+    }
+
+    void putProperty(JSArray* array, unsigned propertyName, JSValue value)
+    {
+        array->put(m_exec, propertyName, value);
+    }
+
+    void putProperty(JSObject* object, const RefPtr<StringImpl> propertyName, JSValue value)
+    {
+        object->putDirect(Identifier(m_exec, String(propertyName)), value);
+    }
+
+    bool startArray(RefPtr<SerializedArray>, JSArray* outArray)
+    {
+        m_gcBuffer.append(outArray);
+        return true;
+    }
+    void endArray(RefPtr<SerializedArray>, JSArray*)
+    {
+        m_gcBuffer.removeLast();
+    }
+    bool startObject(RefPtr<SerializedObject>, JSObject* outObject)
+    {
+        m_gcBuffer.append(outObject);
+        return true;
+    }
+    void endObject(RefPtr<SerializedObject>, JSObject*)
+    {
+        m_gcBuffer.removeLast();
+    }
+
+private:
+    bool m_mustCopy;
+};
+
+JSValue SerializedScriptValueData::deserialize(ExecState* exec, bool mustCopy) const
+{
+    DeserializingTreeWalker context(exec, mustCopy);
+    return walk<DeserializingTreeWalker>(context, *this);
+}
+
+struct TeardownTreeWalker {
+    typedef SerializedScriptValueData InputType;
+    typedef RefPtr<SerializedArray> InputArray;
+    typedef RefPtr<SerializedObject> InputObject;
+    typedef bool OutputType;
+    typedef bool OutputArray;
+    typedef bool OutputObject;
+    typedef SerializedObject::PropertyNameList PropertyList;
+
+    bool shouldTerminate()
+    {
+        return false;
+    }
+
+    unsigned ticksUntilNextCheck()
+    {
+        return 0xFFFFFFFF;
+    }
+
+    bool didTimeOut()
+    {
+        return false;
+    }
+
+    void throwStackOverflow()
+    {
+    }
+
+    void throwInterruptedException()
+    {
+    }
+
+    bool null() { return false; }
+
+    bool isArray(const SerializedScriptValueData& value)
+    {
+        return value.type() == SerializedScriptValueData::ArrayType;
+    }
+
+    bool isObject(const SerializedScriptValueData& value)
+    {
+        return value.type() == SerializedScriptValueData::ObjectType;
+    }
+
+    SerializedArray* asInputArray(const SerializedScriptValueData& value)
+    {
+        return value.asArray();
+    }
+
+    SerializedObject* asInputObject(const SerializedScriptValueData& value)
+    {
+        return value.asObject();
+    }
+
+    bool createOutputArray(unsigned)
+    {
+        return false;
+    }
+
+    bool createOutputObject()
+    {
+        return false;
+    }
+
+    uint32_t length(RefPtr<SerializedArray> array)
+    {
+        return array->length();
+    }
+
+    bool canDoFastRead(RefPtr<SerializedArray> array, unsigned index)
+    {
+        return array->canDoFastRead(index);
+    }
+
+    SerializedScriptValueData getIndex(RefPtr<SerializedArray> array, unsigned index)
+    {
+        return array->getIndex(index);
+    }
+
+    SerializedScriptValueData getSparseIndex(RefPtr<SerializedArray> array, unsigned propertyName, bool& hasIndex)
+    {
+        return array->getSparseIndex(propertyName, hasIndex);
+    }
+
+    SerializedScriptValueData getProperty(RefPtr<SerializedObject> object, const RefPtr<StringImpl>& propertyName, unsigned propertyIndex)
+    {
+        ASSERT(object->names()[propertyIndex] == propertyName);
+        return object->values()[propertyIndex];
+    }
+
+    bool convertIfTerminal(SerializedScriptValueData& value)
+    {
+        switch (value.type()) {
+            case SerializedScriptValueData::ArrayType:
+            case SerializedScriptValueData::ObjectType:
+                return false;
+            case SerializedScriptValueData::StringType:
+            case SerializedScriptValueData::ImmediateType:
+            case SerializedScriptValueData::NumberType:
+                return true;
+            default:
+                ASSERT_NOT_REACHED();
+                return JSValue();
+        }
+    }
+
+    void getPropertyNames(RefPtr<SerializedObject> object, Vector<SerializedObject::PropertyNameList, 16>& properties)
+    {
+        properties.append(object->names());
+    }
+
+    void putProperty(bool, unsigned, bool)
+    {
+    }
+
+    void putProperty(bool, const RefPtr<StringImpl>&, bool)
+    {
+    }
+
+    bool startArray(RefPtr<SerializedArray>, bool)
+    {
+        return true;
+    }
+    void endArray(RefPtr<SerializedArray> array, bool)
+    {
+        array->clear();
+    }
+    bool startObject(RefPtr<SerializedObject>, bool)
+    {
+        return true;
+    }
+    void endObject(RefPtr<SerializedObject> object, bool)
+    {
+        object->clear();
+    }
+};
+
+void SerializedScriptValueData::tearDownSerializedData()
+{
+    if (m_sharedData && m_sharedData->refCount() > 1)
+        return;
+    TeardownTreeWalker context;
+    walk<TeardownTreeWalker>(context, *this);
+}
+
+}
diff --git a/WebCore/bindings/js/SerializedScriptValue.h b/WebCore/bindings/js/SerializedScriptValue.h
new file mode 100644
index 0000000..74c5962
--- /dev/null
+++ b/WebCore/bindings/js/SerializedScriptValue.h
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef SerializedScriptValue_h
+#define SerializedScriptValue_h
+
+#include "ScriptValue.h"
+
+namespace WebCore {
+    class SerializedObject;
+    class SerializedArray;
+
+    class SharedSerializedData : public RefCounted<SharedSerializedData>
+    {
+    public:
+        virtual ~SharedSerializedData() { }
+        SerializedArray* asArray();
+        SerializedObject* asObject();
+    };
+
+    class SerializedScriptValue;
+
+    class SerializedScriptValueData
+    {
+    public:
+        enum SerializedType {
+            EmptyType,
+            DateType,
+            NumberType,
+            ImmediateType,
+            ObjectType,
+            ArrayType,
+            StringType
+        };
+
+        SerializedType type() const { return m_type; }
+        static SerializedScriptValueData serialize(JSC::ExecState*, JSC::JSValue);
+        JSC::JSValue deserialize(JSC::ExecState*, bool mustCopy) const;
+
+        ~SerializedScriptValueData()
+        {
+            if (m_sharedData)
+                tearDownSerializedData();
+        }
+
+        SerializedScriptValueData()
+            : m_type(EmptyType)
+        {
+        }
+
+        explicit SerializedScriptValueData(const String& string)
+            : m_type(StringType)
+            , m_string(string.crossThreadString()) // FIXME: Should be able to just share the Rep
+        {
+        }
+
+        explicit SerializedScriptValueData(JSC::JSValue value)
+            : m_type(ImmediateType)
+        {
+            ASSERT(!value.isCell());
+            m_data.m_immediate = JSC::JSValue::encode(value);
+        }
+
+        SerializedScriptValueData(SerializedType type, double value)
+            : m_type(type)
+        {
+            m_data.m_double = value;
+        }
+
+        SerializedScriptValueData(RefPtr<SerializedObject>);
+        SerializedScriptValueData(RefPtr<SerializedArray>);
+
+        JSC::JSValue asImmediate() const
+        {
+            ASSERT(m_type == ImmediateType);
+            return JSC::JSValue::decode(m_data.m_immediate);
+        }
+
+        double asDouble() const
+        {
+            ASSERT(m_type == NumberType || m_type == DateType);
+            return m_data.m_double;
+        }
+
+        String asString() const
+        {
+            ASSERT(m_type == StringType);
+            return m_string;
+        }
+
+        SerializedObject* asObject() const
+        {
+            ASSERT(m_type == ObjectType);
+            ASSERT(m_sharedData);
+            return m_sharedData->asObject();
+        }
+
+        SerializedArray* asArray() const
+        {
+            ASSERT(m_type == ArrayType);
+            ASSERT(m_sharedData);
+            return m_sharedData->asArray();
+        }
+
+        operator bool () const { return m_type != EmptyType; }
+
+        SerializedScriptValueData release() {
+            SerializedScriptValueData result = *this;
+            *this = SerializedScriptValueData();
+            return result;
+        }
+
+    private:
+        void tearDownSerializedData();
+        SerializedType m_type;
+        RefPtr<SharedSerializedData> m_sharedData;
+        String m_string;
+        union {
+            double m_double;
+            JSC::EncodedJSValue m_immediate;
+        } m_data;
+    };
+
+    class SerializedScriptValue : public RefCounted<SerializedScriptValue>
+    {
+    public:
+        static PassRefPtr<SerializedScriptValue> create(JSC::ExecState* exec, JSC::JSValue value)
+        {
+            return adoptRef(new SerializedScriptValue(SerializedScriptValueData::serialize(exec, value)));
+        }
+
+        static PassRefPtr<SerializedScriptValue> create(String string)
+        {
+            return adoptRef(new SerializedScriptValue(SerializedScriptValueData(string)));
+        }
+
+        static PassRefPtr<SerializedScriptValue> create()
+        {
+            return adoptRef(new SerializedScriptValue(SerializedScriptValueData()));
+        }
+
+        PassRefPtr<SerializedScriptValue> release() {
+            PassRefPtr<SerializedScriptValue> result = adoptRef(new SerializedScriptValue(m_value));
+            m_value = SerializedScriptValueData();
+            result->m_mustCopy = true;
+            return result;
+        }
+
+        String toString() {
+            if (m_value.type() != SerializedScriptValueData::StringType)
+                return "";
+            return m_value.asString();
+        }
+
+        JSC::JSValue deserialize(JSC::ExecState* exec)
+        {
+            if (!m_value)
+                return JSC::jsNull();
+            return m_value.deserialize(exec, m_mustCopy);
+        }
+
+        ~SerializedScriptValue()
+        {
+        }
+    private:
+        SerializedScriptValue(SerializedScriptValueData value)
+            : m_value(value)
+            , m_mustCopy(false)
+        {
+        }
+        SerializedScriptValueData m_value;
+        bool m_mustCopy;
+    };
+}
+
+#endif