dom/bindings/Utils.h
author Boris Zbarsky <bzbarsky@mit.edu>
Wed, 01 Feb 2012 10:25:58 -0500
changeset 86005 98112293e26376b44af89c19f570b8b3140fdca1
parent 86003 771825d1497dfaf98368c8d872c1952c9a134a95
child 86460 16141e260c6120a000bf9ebddf3fb4a3da6b03ae
permissions -rw-r--r--
DOM protos need a custom stringification, so need to have a custom class. Also, improve infrastruture for adding [NoInterfaceObject] support once the parser tells us about it

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
/* vim: set ts=2 sw=2 et tw=79: */
/* 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/. */

#ifndef mozilla_dom_bindings_Utils_h__
#define mozilla_dom_bindings_Utils_h__

#include "jsapi.h"
#include "DOMJSClass.h"
#include "XPCQuickStubs.h"
#include "XPCWrapper.h"
#include "PrototypeList.h"

/* All DOM globals must have a slot at DOM_PROTOTYPE_SLOT */
#define DOM_PROTOTYPE_SLOT (JSCLASS_GLOBAL_SLOT_COUNT + 1)

namespace mozilla {
namespace dom {
namespace bindings {

inline bool
IsDOMClass(JSClass *clasp)
{
  return clasp->flags & JSCLASS_IS_DOMJSCLASS;
}

template<class T>
inline T*
UnwrapDOMObject(JSObject *obj)
{
  MOZ_ASSERT(IsDOMClass(js::GetObjectJSClass(obj)));
  return static_cast<T*>(js::GetReservedSlot(obj,
                                             DOM_OBJECT_SLOT).toPrivate());
}

/*
 * - protoID is the ID of the prototype that corresponds to type T
 * - protoIDIndex is the index at which we expect to find protoID in the DOM
 *                class's mInterfaceChain.
 */
template<class T>
inline nsresult
UnwrapObject(JSContext *cx,
             JSObject *obj,
             prototypes::ID protoID,
             prototypes::Depth protoIDIndex,
             T **value)
{
  /* First check to see whether we have a DOM object */
  JSClass *clasp = js::GetObjectJSClass(obj);
  if (!IsDOMClass(clasp)) {
    /* Maybe we have a security wrapper or outer window? */
    if (!js::IsWrapper(obj)) {
      /* Not a DOM object, not a wrapper, just bail */
      return NS_ERROR_XPC_BAD_CONVERT_JS;
    }

    obj = XPCWrapper::Unwrap(cx, obj, false);
    if (!obj) {
      return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
    }
    MOZ_ASSERT(!js::IsWrapper(obj));
    clasp = js::GetObjectJSClass(obj);
    if (!IsDOMClass(clasp)) {
      /* We don't have a DOM object */
      return NS_ERROR_XPC_BAD_CONVERT_JS;
    }
  }

  MOZ_ASSERT(IsDOMClass(clasp));

  /* This object is a DOM object.  Double-check that it is safely
     castable to T by checking whether it claims to inherit from the
     class identified by protoID. */
  DOMJSClass *domClass = DOMJSClass::FromJSClass(clasp);
  if (domClass->mInterfaceChain[protoIDIndex] == protoID) {
    *value = UnwrapDOMObject<T>(obj);
    return NS_OK;
  }

  /* It's the wrong sort of DOM object */
  return NS_ERROR_XPC_BAD_CONVERT_JS;
}

template<class T>
inline bool
UnwrapThis(JSContext *cx,
           JSObject *obj,
           prototypes::ID protoID,
           prototypes::Depth protoIDIndex,
           T **value)
{
  nsresult rv = UnwrapObject(cx, obj, protoID, protoIDIndex, value);
  if (NS_FAILED(rv)) {
    return xpc_qsThrow(cx, rv);
  }
  return true;
}

template<class T>
inline nsresult
UnwrapInterfaceArg(JSContext *cx,
                   jsval v,
                   prototypes::ID protoID,
                   prototypes::Depth protoIDIndex,
                   T **value,
                   nsISupports **argRef,
                   jsval *vp)
{
  if (v.isObject()) {
    // Fast-path the case when we have a DOM object.
    nsresult rv = UnwrapObject(cx, &v.toObject(), protoID, protoIDIndex, value);
    if (NS_SUCCEEDED(rv)) {
      // It was a DOM object.  We're done.
      *vp = v;
      return rv;
    }
  
    if (rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO) {
      // This is a fatal failure; just return
      return rv;
    }
  }

  // Fall back on unwrapping old-style arguments (possibly including
  // nodelist bindings).
  return xpc_qsUnwrapArg(cx, v, value, argRef, vp);
}

inline JSObject **
GetProtoArray(JSObject *global)
{
  MOZ_ASSERT(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL);
  return static_cast<JSObject**>(
    js::GetReservedSlot(global, DOM_PROTOTYPE_SLOT).toPrivate());
}

inline void
AllocateProtoCache(JSObject *obj)
{
  MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL);
  // Important: The () at the end ensure zero-initialization
  JSObject** protoArray = new JSObject*[prototypes::id::Count]();
  js::SetReservedSlot(obj, DOM_PROTOTYPE_SLOT, JS::PrivateValue(protoArray));
}

inline void
DestroyProtoCache(JSObject *obj)
{
  JSObject **protoArray = GetProtoArray(obj);
  delete [] protoArray;
}

inline JSObject*
GetCanonicalProto(JSContext *cx, JSObject *global, JSProtoKey protoKey)
{
  JSObject* proto;
  if (!js_GetClassPrototype(cx, global, protoKey, &proto)) {
    return NULL;
  }
  return proto;
}

/*
 * Create a DOM prototype object.
 *
 * parentProto is the prototype our new object should use.
 * protoClass is the JSClass our new object should use.
 * constructorClass is the class to use for the corresponding interface object.
 *                  This is null if we should not create an interface object.
 * methods and properties are to be defined on the prototype; these arguments
 *                        are allowed to be null if there are no methods or
 *                        properties respectively.
 * If constructorClass is non-null, The resulting constructor object will be
 * defined on the given global with property name |name|, which must also be
 * non-null.
 *
 * The return value is the newly-created prototype object.
 */
JSObject*
CreateProtoObject(JSContext *cx, JSObject *parentProto,
                  JSClass *protoClass,
                  JSClass *constructorClass,
                  JSFunctionSpec *methods,
                  JSPropertySpec *properties,
                  JSObject *global,
                  const char* name);

template<class T>
inline bool
WrapNewBindingObject(JSContext *cx, JSObject *scope, T *value, jsval *vp)
{
  JSObject *obj = value->GetWrapper();
  if (obj)
    return obj;

  bool enabled;
  obj = value->WrapObject(cx, scope, &enabled);
  if (enabled) {
    *vp = OBJECT_TO_JSVAL(obj);
    return obj != NULL;
  }

  return WrapOtherObject(cx, scope, value, vp);
}

template<class T>
inline bool
WrapOtherObject(JSContext *cx, JSObject *scope, T *value, jsval *vp)
{
  XPCLazyCallContext lccx(JS_CALLER, cx, scope);

  xpcObjectHelper helper(value);
  nsresult rv;
  if (!XPCConvert::NativeInterface2JSObject(lccx, vp, NULL, helper, NULL, NULL,
                                            true, OBJ_IS_NOT_GLOBAL, &rv)) {
    if (!JS_IsExceptionPending(cx))
      XPCThrower::Throw(rv, cx);
    return false;
  }

  return true;
}

} // namespace bindings
} // namespace dom
} // namespace mozilla

#endif /* mozilla_dom_bindings_Utils_h__ */