js/ipc/WrapperOwner.cpp
author Randell Jesup <rjesup@jesup.org>
Tue, 30 Sep 2014 16:11:16 -0400
changeset 288354 002f9202019b209a0902b3a5b86ec3fc631455ce
parent 193505 f8be8f1a923bc543192df7be2a5414e702608c42
child 201288 184bbe9eacf4b6c60ecbdc8baf5fece63b111b75
permissions -rw-r--r--
Bug 1074548 - Lazy-allocate webrtc trace buffers in RELEASE_BUILDs. r=ted, a=sledru

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=4 sw=4 et tw=80:
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "WrapperOwner.h"
#include "JavaScriptLogging.h"
#include "mozilla/unused.h"
#include "mozilla/dom/BindingUtils.h"
#include "jsfriendapi.h"
#include "xpcprivate.h"

using namespace js;
using namespace JS;
using namespace mozilla;
using namespace mozilla::jsipc;

WrapperOwner::WrapperOwner(JSRuntime *rt)
  : JavaScriptShared(rt),
    inactive_(false)
{
}

static inline WrapperOwner *
OwnerOf(JSObject *obj)
{
    MOZ_ASSERT(IsCPOW(obj));
    return reinterpret_cast<WrapperOwner *>(GetProxyExtra(obj, 0).toPrivate());
}

ObjectId
WrapperOwner::idOf(JSObject *obj)
{
    MOZ_ASSERT(IsCPOW(obj));

    Value v = GetProxyExtra(obj, 1);
    MOZ_ASSERT(v.isDouble());

    ObjectId objId = BitwiseCast<uint64_t>(v.toDouble());
    MOZ_ASSERT(findCPOWById(objId) == obj);
    MOZ_ASSERT(objId);

    return objId;
}

int sCPOWProxyHandler;

class CPOWProxyHandler : public BaseProxyHandler
{
  public:
    CPOWProxyHandler()
      : BaseProxyHandler(&sCPOWProxyHandler) {}
    virtual ~CPOWProxyHandler() {}

    virtual bool finalizeInBackground(Value priv) const MOZ_OVERRIDE {
        return false;
    }

    virtual bool preventExtensions(JSContext *cx, HandleObject proxy) const MOZ_OVERRIDE;
    virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                       MutableHandle<JSPropertyDescriptor> desc) const MOZ_OVERRIDE;
    virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                          MutableHandle<JSPropertyDescriptor> desc) const MOZ_OVERRIDE;
    virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
                                MutableHandle<JSPropertyDescriptor> desc) const MOZ_OVERRIDE;
    virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy,
                                     AutoIdVector &props) const MOZ_OVERRIDE;
    virtual bool delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) const MOZ_OVERRIDE;
    virtual bool enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) const MOZ_OVERRIDE;

    virtual bool has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) const MOZ_OVERRIDE;
    virtual bool hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) const MOZ_OVERRIDE;
    virtual bool get(JSContext *cx, HandleObject proxy, HandleObject receiver,
                     HandleId id, MutableHandleValue vp) const MOZ_OVERRIDE;
    virtual bool set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
                     JS::HandleId id, bool strict, JS::MutableHandleValue vp) const MOZ_OVERRIDE;
    virtual bool keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) const MOZ_OVERRIDE;

    virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) const MOZ_OVERRIDE;
    virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) const MOZ_OVERRIDE;
    virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) const MOZ_OVERRIDE;
    virtual bool objectClassIs(HandleObject obj, js::ESClassValue classValue,
                               JSContext *cx) const MOZ_OVERRIDE;
    virtual const char* className(JSContext *cx, HandleObject proxy) const MOZ_OVERRIDE;
    virtual void finalize(JSFreeOp *fop, JSObject *proxy) const MOZ_OVERRIDE;

    static const CPOWProxyHandler singleton;
};

const CPOWProxyHandler CPOWProxyHandler::singleton;

#define FORWARD(call, args)                                             \
    WrapperOwner *owner = OwnerOf(proxy);                               \
    if (!owner->active()) {                                             \
        JS_ReportError(cx, "cannot use a CPOW whose process is gone");  \
        return false;                                                   \
    }                                                                   \
    return owner->call args;

bool
CPOWProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy) const
{
    FORWARD(preventExtensions, (cx, proxy));
}

bool
WrapperOwner::preventExtensions(JSContext *cx, HandleObject proxy)
{
    ObjectId objId = idOf(proxy);

    ReturnStatus status;
    if (!CallPreventExtensions(objId, &status))
        return ipcfail(cx);

    LOG_STACK();

    return ok(cx, status);
}

bool
CPOWProxyHandler::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                        MutableHandle<JSPropertyDescriptor> desc) const
{
    FORWARD(getPropertyDescriptor, (cx, proxy, id, desc));
}

bool
WrapperOwner::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
				    MutableHandle<JSPropertyDescriptor> desc)
{
    ObjectId objId = idOf(proxy);

    nsString idstr;
    if (!convertIdToGeckoString(cx, id, &idstr))
        return false;

    ReturnStatus status;
    PPropertyDescriptor result;
    if (!CallGetPropertyDescriptor(objId, idstr, &status, &result))
        return ipcfail(cx);

    LOG_STACK();

    if (!ok(cx, status))
        return false;

    return toDescriptor(cx, result, desc);
}

bool
CPOWProxyHandler::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                           MutableHandle<JSPropertyDescriptor> desc) const
{
    FORWARD(getOwnPropertyDescriptor, (cx, proxy, id, desc));
}

bool
WrapperOwner::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
				       MutableHandle<JSPropertyDescriptor> desc)
{
    ObjectId objId = idOf(proxy);

    nsString idstr;
    if (!convertIdToGeckoString(cx, id, &idstr))
        return false;

    ReturnStatus status;
    PPropertyDescriptor result;
    if (!CallGetOwnPropertyDescriptor(objId, idstr, &status, &result))
        return ipcfail(cx);

    LOG_STACK();

    if (!ok(cx, status))
        return false;

    return toDescriptor(cx, result, desc);
}

bool
CPOWProxyHandler::defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
                                 MutableHandle<JSPropertyDescriptor> desc) const
{
    FORWARD(defineProperty, (cx, proxy, id, desc));
}

bool
WrapperOwner::defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
			     MutableHandle<JSPropertyDescriptor> desc)
{
    ObjectId objId = idOf(proxy);

    nsString idstr;
    if (!convertIdToGeckoString(cx, id, &idstr))
        return false;

    PPropertyDescriptor descriptor;
    if (!fromDescriptor(cx, desc, &descriptor))
        return false;

    ReturnStatus status;
    if (!CallDefineProperty(objId, idstr, descriptor, &status))
        return ipcfail(cx);

    LOG_STACK();

    return ok(cx, status);
}

bool
CPOWProxyHandler::getOwnPropertyNames(JSContext *cx, HandleObject proxy,
                                      AutoIdVector &props) const
{
    FORWARD(getOwnPropertyNames, (cx, proxy, props));
}

bool
WrapperOwner::getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props)
{
    return getPropertyNames(cx, proxy, JSITER_OWNONLY | JSITER_HIDDEN, props);
}

bool
CPOWProxyHandler::delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) const
{
    FORWARD(delete_, (cx, proxy, id, bp));
}

bool
WrapperOwner::delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
    ObjectId objId = idOf(proxy);

    nsString idstr;
    if (!convertIdToGeckoString(cx, id, &idstr))
        return false;

    ReturnStatus status;
    if (!CallDelete(objId, idstr, &status, bp))
        return ipcfail(cx);

    LOG_STACK();

    return ok(cx, status);
}

bool
CPOWProxyHandler::enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) const
{
    FORWARD(enumerate, (cx, proxy, props));
}

bool
WrapperOwner::enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props)
{
    return getPropertyNames(cx, proxy, 0, props);
}

bool
CPOWProxyHandler::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) const
{
    FORWARD(has, (cx, proxy, id, bp));
}

bool
WrapperOwner::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
    ObjectId objId = idOf(proxy);

    nsString idstr;
    if (!convertIdToGeckoString(cx, id, &idstr))
        return false;

    ReturnStatus status;
    if (!CallHas(objId, idstr, &status, bp))
        return ipcfail(cx);

    LOG_STACK();

    return ok(cx, status);
}

bool
CPOWProxyHandler::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) const
{
    FORWARD(hasOwn, (cx, proxy, id, bp));
}

bool
WrapperOwner::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
    ObjectId objId = idOf(proxy);

    nsString idstr;
    if (!convertIdToGeckoString(cx, id, &idstr))
        return false;

    ReturnStatus status;
    if (!CallHasOwn(objId, idstr, &status, bp))
        return ipcfail(cx);

    LOG_STACK();

    return !!ok(cx, status);
}

bool
CPOWProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObject receiver,
                      HandleId id, MutableHandleValue vp) const
{
    FORWARD(get, (cx, proxy, receiver, id, vp));
}

static bool
CPOWToString(JSContext *cx, unsigned argc, Value *vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    RootedObject callee(cx, &args.callee());
    RootedValue cpowValue(cx);
    if (!JS_LookupProperty(cx, callee, "__cpow__", &cpowValue))
        return false;

    if (!cpowValue.isObject() || !IsCPOW(&cpowValue.toObject())) {
        JS_ReportError(cx, "CPOWToString called on an incompatible object");
        return false;
    }

    RootedObject proxy(cx, &cpowValue.toObject());
    FORWARD(toString, (cx, proxy, args));
}

bool
WrapperOwner::toString(JSContext *cx, HandleObject cpow, JS::CallArgs &args)
{
    // Ask the other side to call its toString method. Update the callee so that
    // it points to the CPOW and not to the synthesized CPOWToString function.
    args.setCallee(ObjectValue(*cpow));
    if (!callOrConstruct(cx, cpow, args, false))
        return false;

    if (!args.rval().isString())
        return true;

    RootedString cpowResult(cx, args.rval().toString());
    nsAutoJSString toStringResult;
    if (!toStringResult.init(cx, cpowResult))
        return false;

    // We don't want to wrap toString() results for things like the location
    // object, where toString() is supposed to return a URL and nothing else.
    nsAutoString result;
    if (toStringResult[0] == '[') {
        result.AppendLiteral("[object CPOW ");
        result += toStringResult;
        result.AppendLiteral("]");
    } else {
        result += toStringResult;
    }

    JSString *str = JS_NewUCStringCopyN(cx, result.get(), result.Length());
    if (!str)
        return false;

    args.rval().setString(str);
    return true;
}

bool
WrapperOwner::get(JSContext *cx, HandleObject proxy, HandleObject receiver,
		  HandleId id, MutableHandleValue vp)
{
    ObjectId objId = idOf(proxy);

    ObjectVariant receiverVar;
    if (!toObjectVariant(cx, receiver, &receiverVar))
        return false;

    nsString idstr;
    if (!convertIdToGeckoString(cx, id, &idstr))
        return false;

    JSVariant val;
    ReturnStatus status;
    if (!CallGet(objId, receiverVar, idstr, &status, &val))
        return ipcfail(cx);

    LOG_STACK();

    if (!ok(cx, status))
        return false;

    if (!fromVariant(cx, val, vp))
        return false;

    if (idstr.EqualsLiteral("toString")) {
        RootedFunction toString(cx, JS_NewFunction(cx, CPOWToString, 0, 0, proxy, "toString"));
        if (!toString)
            return false;

        RootedObject toStringObj(cx, JS_GetFunctionObject(toString));

        if (!JS_DefineProperty(cx, toStringObj, "__cpow__", vp, JSPROP_PERMANENT | JSPROP_READONLY))
            return false;

        vp.set(ObjectValue(*toStringObj));
    }

    return true;
}

bool
CPOWProxyHandler::set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
                      JS::HandleId id, bool strict, JS::MutableHandleValue vp) const
{
    FORWARD(set, (cx, proxy, receiver, id, strict, vp));
}

bool
WrapperOwner::set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
		  JS::HandleId id, bool strict, JS::MutableHandleValue vp)
{
    ObjectId objId = idOf(proxy);

    ObjectVariant receiverVar;
    if (!toObjectVariant(cx, receiver, &receiverVar))
        return false;

    nsString idstr;
    if (!convertIdToGeckoString(cx, id, &idstr))
        return false;

    JSVariant val;
    if (!toVariant(cx, vp, &val))
        return false;

    ReturnStatus status;
    JSVariant result;
    if (!CallSet(objId, receiverVar, idstr, strict, val, &status, &result))
        return ipcfail(cx);

    LOG_STACK();

    if (!ok(cx, status))
        return false;

    return fromVariant(cx, result, vp);
}

bool
CPOWProxyHandler::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) const
{
    FORWARD(keys, (cx, proxy, props));
}

bool
WrapperOwner::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props)
{
    return getPropertyNames(cx, proxy, JSITER_OWNONLY, props);
}

bool
CPOWProxyHandler::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) const
{
    FORWARD(isExtensible, (cx, proxy, extensible));
}

bool
WrapperOwner::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible)
{
    ObjectId objId = idOf(proxy);

    ReturnStatus status;
    if (!CallIsExtensible(objId, &status, extensible))
        return ipcfail(cx);

    LOG_STACK();

    return ok(cx, status);
}

bool
CPOWProxyHandler::call(JSContext *cx, HandleObject proxy, const CallArgs &args) const
{
    FORWARD(callOrConstruct, (cx, proxy, args, false));
}

bool
CPOWProxyHandler::construct(JSContext *cx, HandleObject proxy, const CallArgs &args) const
{
    FORWARD(callOrConstruct, (cx, proxy, args, true));
}

bool
WrapperOwner::callOrConstruct(JSContext *cx, HandleObject proxy, const CallArgs &args,
                              bool construct)
{
    ObjectId objId = idOf(proxy);

    InfallibleTArray<JSParam> vals;
    AutoValueVector outobjects(cx);

    RootedValue v(cx);
    for (size_t i = 0; i < args.length() + 2; i++) {
        // The |this| value for constructors is a magic value that we won't be
        // able to convert, so skip it.
        if (i == 1 && construct)
            v = UndefinedValue();
        else
            v = args.base()[i];
        if (v.isObject()) {
            RootedObject obj(cx, &v.toObject());
            if (xpc::IsOutObject(cx, obj)) {
                // Make sure it is not an in-out object.
                bool found;
                if (!JS_HasProperty(cx, obj, "value", &found))
                    return false;
                if (found) {
                    JS_ReportError(cx, "in-out objects cannot be sent via CPOWs yet");
                    return false;
                }

                vals.AppendElement(JSParam(void_t()));
                if (!outobjects.append(ObjectValue(*obj)))
                    return false;
                continue;
            }
        }
        JSVariant val;
        if (!toVariant(cx, v, &val))
            return false;
        vals.AppendElement(JSParam(val));
    }

    JSVariant result;
    ReturnStatus status;
    InfallibleTArray<JSParam> outparams;
    if (!CallCallOrConstruct(objId, vals, construct, &status, &result, &outparams))
        return ipcfail(cx);

    LOG_STACK();

    if (!ok(cx, status))
        return false;

    if (outparams.Length() != outobjects.length())
        return ipcfail(cx);

    RootedObject obj(cx);
    for (size_t i = 0; i < outparams.Length(); i++) {
        // Don't bother doing anything for outparams that weren't set.
        if (outparams[i].type() == JSParam::Tvoid_t)
            continue;

        // Take the value the child process returned, and set it on the XPC
        // object.
        if (!fromVariant(cx, outparams[i], &v))
            return false;

        obj = &outobjects[i].toObject();
        if (!JS_SetProperty(cx, obj, "value", v))
            return false;
    }

    if (!fromVariant(cx, result, args.rval()))
        return false;

    return true;
}


bool
CPOWProxyHandler::objectClassIs(HandleObject proxy, js::ESClassValue classValue, JSContext *cx) const
{
    FORWARD(objectClassIs, (cx, proxy, classValue));
}

bool
WrapperOwner::objectClassIs(JSContext *cx, HandleObject proxy, js::ESClassValue classValue)
{
    ObjectId objId = idOf(proxy);

    // This function is assumed infallible, so we just return false if the IPC
    // channel fails.
    bool result;
    if (!CallObjectClassIs(objId, classValue, &result))
        return false;

    LOG_STACK();

    return result;
}

const char *
CPOWProxyHandler::className(JSContext *cx, HandleObject proxy) const
{
    WrapperOwner *parent = OwnerOf(proxy);
    if (!parent->active())
        return "<dead CPOW>";
    return parent->className(cx, proxy);
}

const char *
WrapperOwner::className(JSContext *cx, HandleObject proxy)
{
    ObjectId objId = idOf(proxy);

    nsString name;
    if (!CallClassName(objId, &name))
        return "<error>";

    LOG_STACK();

    return ToNewCString(name);
}

void
CPOWProxyHandler::finalize(JSFreeOp *fop, JSObject *proxy) const
{
    OwnerOf(proxy)->drop(proxy);
}

void
WrapperOwner::drop(JSObject *obj)
{
    ObjectId objId = idOf(obj);

    cpows_.remove(objId);
    if (active())
        unused << SendDropObject(objId);
    decref();
}

bool
WrapperOwner::init()
{
    if (!JavaScriptShared::init())
        return false;

    return true;
}

bool
WrapperOwner::getPropertyNames(JSContext *cx, HandleObject proxy, uint32_t flags, AutoIdVector &props)
{
    ObjectId objId = idOf(proxy);

    ReturnStatus status;
    InfallibleTArray<nsString> names;
    if (!CallGetPropertyNames(objId, flags, &status, &names))
        return ipcfail(cx);

    LOG_STACK();

    if (!ok(cx, status))
        return false;

    for (size_t i = 0; i < names.Length(); i++) {
        RootedId name(cx);
        if (!convertGeckoStringToId(cx, names[i], &name))
            return false;
        if (!props.append(name))
            return false;
    }

    return true;
}

namespace mozilla {
namespace jsipc {

bool
IsCPOW(JSObject *obj)
{
    return IsProxy(obj) && GetProxyHandler(obj) == &CPOWProxyHandler::singleton;
}

bool
IsWrappedCPOW(JSObject *obj)
{
    JSObject *unwrapped = js::CheckedUnwrap(obj, true);
    if (!unwrapped)
        return false;
    return IsCPOW(unwrapped);
}

nsresult
InstanceOf(JSObject *proxy, const nsID *id, bool *bp)
{
    WrapperOwner *parent = OwnerOf(proxy);
    if (!parent->active())
        return NS_ERROR_UNEXPECTED;
    return parent->instanceOf(proxy, id, bp);
}

bool
DOMInstanceOf(JSContext *cx, JSObject *proxy, int prototypeID, int depth, bool *bp)
{
    FORWARD(domInstanceOf, (cx, proxy, prototypeID, depth, bp));
}

} /* namespace jsipc */
} /* namespace mozilla */

nsresult
WrapperOwner::instanceOf(JSObject *obj, const nsID *id, bool *bp)
{
    ObjectId objId = idOf(obj);

    JSIID iid;
    ConvertID(*id, &iid);

    ReturnStatus status;
    if (!CallInstanceOf(objId, iid, &status, bp))
        return NS_ERROR_UNEXPECTED;

    if (status.type() != ReturnStatus::TReturnSuccess)
        return NS_ERROR_UNEXPECTED;

    return NS_OK;
}

bool
WrapperOwner::domInstanceOf(JSContext *cx, JSObject *obj, int prototypeID, int depth, bool *bp)
{
    ObjectId objId = idOf(obj);

    ReturnStatus status;
    if (!CallDOMInstanceOf(objId, prototypeID, depth, &status, bp))
        return ipcfail(cx);

    LOG_STACK();

    return ok(cx, status);
}

void
WrapperOwner::ActorDestroy(ActorDestroyReason why)
{
    inactive_ = true;
}

bool
WrapperOwner::ipcfail(JSContext *cx)
{
    JS_ReportError(cx, "child process crashed or timedout");
    return false;
}

bool
WrapperOwner::ok(JSContext *cx, const ReturnStatus &status)
{
    if (status.type() == ReturnStatus::TReturnSuccess)
        return true;

    if (status.type() == ReturnStatus::TReturnStopIteration)
        return JS_ThrowStopIteration(cx);

    RootedValue exn(cx);
    if (!fromVariant(cx, status.get_ReturnException().exn(), &exn))
        return false;

    JS_SetPendingException(cx, exn);
    return false;
}

bool
WrapperOwner::toObjectVariant(JSContext *cx, JSObject *objArg, ObjectVariant *objVarp)
{
    RootedObject obj(cx, objArg);
    JS_ASSERT(obj);

    // We always save objects unwrapped in the CPOW table. If we stored
    // wrappers, then the wrapper might be GCed while the target remained alive.
    // Whenever operating on an object that comes from the table, we wrap it
    // in findObjectById.
    obj = js::CheckedUnwrap(obj, false);
    if (obj && IsCPOW(obj) && OwnerOf(obj) == this) {
        *objVarp = LocalObject(idOf(obj));
        return true;
    }

    ObjectId id = objectIds_.find(obj);
    if (id) {
        *objVarp = RemoteObject(id);
        return true;
    }

    // Need to call PreserveWrapper on |obj| in case it's a reflector.
    // FIXME: What if it's an XPCWrappedNative?
    if (mozilla::dom::IsDOMObject(obj))
        mozilla::dom::TryPreserveWrapper(obj);

    id = ++lastId_;
    if (id > MAX_CPOW_IDS) {
        JS_ReportError(cx, "CPOW id limit reached");
        return false;
    }

    id <<= OBJECT_EXTRA_BITS;
    if (JS_ObjectIsCallable(cx, obj))
        id |= OBJECT_IS_CALLABLE;

    if (!objects_.add(id, obj))
        return false;
    if (!objectIds_.add(cx, obj, id))
        return false;

    *objVarp = RemoteObject(id);
    return true;
}

JSObject *
WrapperOwner::fromObjectVariant(JSContext *cx, ObjectVariant objVar)
{
    if (objVar.type() == ObjectVariant::TRemoteObject) {
        return fromRemoteObjectVariant(cx, objVar.get_RemoteObject());
    } else {
        return fromLocalObjectVariant(cx, objVar.get_LocalObject());
    }
}

JSObject *
WrapperOwner::fromRemoteObjectVariant(JSContext *cx, RemoteObject objVar)
{
    ObjectId objId = objVar.id();

    RootedObject obj(cx, findCPOWById(objId));
    if (obj) {
        if (!JS_WrapObject(cx, &obj))
            return nullptr;
        return obj;
    }

    if (objId > MAX_CPOW_IDS) {
        JS_ReportError(cx, "unusable CPOW id");
        return nullptr;
    }

    bool callable = !!(objId & OBJECT_IS_CALLABLE);

    RootedObject global(cx, JS::CurrentGlobalOrNull(cx));

    RootedValue v(cx, UndefinedValue());
    ProxyOptions options;
    options.selectDefaultClass(callable);
    obj = NewProxyObject(cx,
                         &CPOWProxyHandler::singleton,
                         v,
                         nullptr,
                         global,
                         options);
    if (!obj)
        return nullptr;

    if (!cpows_.add(objId, obj))
        return nullptr;

    // Incref once we know the decref will be called.
    incref();

    SetProxyExtra(obj, 0, PrivateValue(this));
    SetProxyExtra(obj, 1, DoubleValue(BitwiseCast<double>(objId)));
    return obj;
}

JSObject *
WrapperOwner::fromLocalObjectVariant(JSContext *cx, LocalObject objVar)
{
    ObjectId id = objVar.id();
    Rooted<JSObject*> obj(cx, findObjectById(cx, id));
    if (!obj)
        return nullptr;
    if (!JS_WrapObject(cx, &obj))
        return nullptr;
    return obj;
}