bool jsval_to_FBInfo(JSContext *cx, jsval v, StringMap* ret)
{
	JSObject* tmp = JSVAL_TO_OBJECT(v);
	if (!tmp) {
		LOGD("jsval_to_TProductInfo: the jsval is not an object.");
		return false;
	}

	JSObject* it = JS_NewPropertyIterator(cx, tmp);

	while (true)
	{
		jsid idp;
		jsval key;
		if (! JS_NextProperty(cx, it, &idp) || ! JS_IdToValue(cx, idp, &key))
			return false; // error
		if (key == JSVAL_VOID)
			break; // end of iteration
		if (! JSVAL_IS_STRING(key))
			continue; // ignore integer properties
		JS::RootedValue value(cx);
		JS_GetPropertyById(cx, tmp, idp, &value);

//		if (! JSVAL_IS_STRING(value))
//			continue; // ignore integer properties
		if(JSVAL_IS_STRING(value))
		{

			JSStringWrapper strWrapper(JSVAL_TO_STRING(key), cx);
			JSStringWrapper strWrapper2(JSVAL_TO_STRING(value), cx);

			ret->insert(std::map<std::string, std::string>::value_type(strWrapper.get(), strWrapper2.get()));
		}
		else if(JSVAL_IS_NUMBER(value))
		{
			double number = 0.0;
			JS::ToNumber(cx, value, &number);

			std::stringstream ss;
			ss << number;

			JSStringWrapper strWrapper(JSVAL_TO_STRING(key), cx);
			//JSStringWrapper strWrapper2(JSVAL_TO_STRING(value), cx);

			ret->insert(std::map<std::string, std::string>::value_type(strWrapper.get(), ss.str()));
		}
		else if(JSVAL_IS_BOOLEAN(value))
		{
			bool boolVal = JS::ToBoolean(value);
			JSStringWrapper strWrapper(JSVAL_TO_STRING(key), cx);
			//JSStringWrapper strWrapper2(JSVAL_TO_STRING(value), cx);
			std::string boolstring = boolVal ? "true" : "false";
			ret->insert(std::map<std::string, std::string>::value_type(strWrapper.get(), boolstring));
		}
	}

	return true;
}
예제 #2
0
파일: util.cpp 프로젝트: aduros/amity
void consumeEvent (JSContext* jsCtx, AmityEvent* event)
{
    jsval fval;
    if (!JS_GetPropertyById(jsCtx, event->obj, event->handler, &fval)) {
        return; // Missing handler
    }

    jsval rval;
    JS_CallFunctionValue(jsCtx, event->obj, fval, 1, &event->param, &rval);
}
예제 #3
0
bool ScriptInterface::GetPropertyInt_(jsval obj, int name, jsval& out)
{
	if (! JSVAL_IS_OBJECT(obj))
		return false;
	JSObject* object = JSVAL_TO_OBJECT(obj);

	if (!JS_GetPropertyById(m->m_cx, object, INT_TO_JSID(name), &out))
		return false;
	return true;
}
예제 #4
0
// Generate 'usage' and 'help' properties for the given object.
// JS_DefineFunctionsWithHelp will define individual function objects with both
// of those properties (eg getpid.usage = "getpid()" and getpid.help = "return
// the process id"). This function will generate strings for an "interface
// object", eg os.file, which contains some number of functions.
//
// .usage will be set to "<name> - interface object".
//
// .help will be set to a newline-separated list of functions that have either
// 'help' or 'usage' properties. Functions are described with their usage
// strings, if they have them, else with just their names.
//
bool
GenerateInterfaceHelp(JSContext* cx, HandleObject obj, const char* name)
{
    AutoIdVector idv(cx);
    if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &idv))
        return false;

    StringBuffer buf(cx);
    int numEntries = 0;
    for (size_t i = 0; i < idv.length(); i++) {
        RootedId id(cx, idv[i]);
        RootedValue v(cx);
        if (!JS_GetPropertyById(cx, obj, id, &v))
            return false;
        if (!v.isObject())
            continue;
        RootedObject prop(cx, &v.toObject());

        RootedValue usage(cx);
        RootedValue help(cx);
        if (!JS_GetProperty(cx, prop, "usage", &usage))
            return false;
        if (!JS_GetProperty(cx, prop, "help", &help))
            return false;
        if (!usage.isString() && !help.isString())
            continue;

        if (numEntries && !buf.append("\n"))
            return false;
        numEntries++;

        if (!buf.append("  ", 2))
            return false;

        if (!buf.append(usage.isString() ? usage.toString() : JSID_TO_FLAT_STRING(id)))
            return false;
    }

    RootedString s(cx, buf.finishString());
    if (!s || !JS_DefineProperty(cx, obj, "help", s, 0))
        return false;

    buf.clear();
    if (!buf.append(name, strlen(name)) || !buf.append(" - interface object with ", 25))
        return false;
    char cbuf[100];
    SprintfLiteral(cbuf, "%d %s", numEntries, numEntries == 1 ? "entry" : "entries");
    if (!buf.append(cbuf, strlen(cbuf)))
        return false;
    s = buf.finishString();
    if (!s || !JS_DefineProperty(cx, obj, "usage", s, 0))
        return false;

    return true;
}
예제 #5
0
static JSBool
XPC_COW_GetOrSetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp,
                         JSBool isSet)
{
  obj = GetWrapper(obj);
  if (!obj) {
    return ThrowException(NS_ERROR_ILLEGAL_VALUE, cx);
  }

  XPCCallContext ccx(JS_CALLER, cx);
  if (!ccx.IsValid()) {
    return ThrowException(NS_ERROR_FAILURE, cx);
  }

  AUTO_MARK_JSVAL(ccx, vp);

  JSObject *wrappedObj = GetWrappedObject(cx, obj);
  if (!wrappedObj) {
    return ThrowException(NS_ERROR_ILLEGAL_VALUE, cx);
  }

  jsid interned_id;
  if (!JS_ValueToId(cx, id, &interned_id)) {
    return JS_FALSE;
  }

  if (interned_id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_PROTO) ||
      interned_id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_PARENT) ||
      interned_id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_EXPOSEDPROPS)) {
    // No getting or setting __proto__ or __parent__ on my object.
    return ThrowException(NS_ERROR_INVALID_ARG, cx); // XXX better error message
  }

  JSBool canTouch;
  if (!CanTouchProperty(cx, obj, interned_id, isSet, &canTouch)) {
    return JS_FALSE;
  }

  if (!canTouch) {
    return ThrowException(NS_ERROR_XPC_SECURITY_MANAGER_VETO, cx);
  }

  if (!XPC_COW_RewrapForChrome(cx, obj, vp)) {
    return JS_FALSE;
  }

  JSBool ok = isSet
              ? JS_SetPropertyById(cx, wrappedObj, interned_id, vp)
              : JS_GetPropertyById(cx, wrappedObj, interned_id, vp);
  if (!ok) {
    return JS_FALSE;
  }

  return XPC_COW_RewrapForContent(cx, obj, vp);
}
예제 #6
0
파일: to_erl.c 프로젝트: chaos-ad/emonk
int
to_erl_object(ErlNifEnv* env, JSContext* cx, JSObject* obj, ERL_NIF_TERM* term)
{
    ERL_NIF_TERM* array = NULL;
    ERL_NIF_TERM list;
    ERL_NIF_TERM keyterm;
    ERL_NIF_TERM valterm;
    JSObject* iter;
    jsid idp;
    jsval val;
    int length;
    int index;
    int ret = ERROR;

    iter = JS_NewPropertyIterator(cx, obj);
    if(iter == NULL) goto done;

    length = 0;
    while(JS_NextProperty(cx, iter, &idp))
    {
        if(idp == JSID_VOID) break;
        length += 1;
    }
    
    array = enif_alloc(length * sizeof(ERL_NIF_TERM));
    if(array == NULL) goto done;
    
    iter = JS_NewPropertyIterator(cx, obj);
    if(iter == NULL) goto done;

    index = 0;
    while(JS_NextProperty(cx, iter, &idp))
    {
        if(idp == JSID_VOID)
        {
            list = enif_make_list_from_array(env, array, length);
            *term = enif_make_tuple1(env, list);
            ret = OK;
            goto done;
        }

        if(!JS_IdToValue(cx, idp, &val)) goto done;
        if(!to_erl_string(env, cx, val, &keyterm)) goto done;
        if(!JS_GetPropertyById(cx, obj, idp, &val)) goto done;
        if(!to_erl_intern(env, cx, val, &valterm)) goto done;
        
        array[index] = enif_make_tuple2(env, keyterm, valterm);
        index += 1;
    }

done:
    if(array != NULL) enif_free(array);
    return ret;
}
예제 #7
0
bool ScriptInterface::GetPropertyInt_(JS::HandleValue obj, int name, JS::MutableHandleValue out)
{
	JSAutoRequest rq(m->m_cx);
	JS::RootedId nameId(m->m_cx, INT_TO_JSID(name));
	if (!obj.isObject())
		return false;
	JS::RootedObject object(m->m_cx, &obj.toObject());
	
	if (!JS_GetPropertyById(m->m_cx, object, nameId, out))
		return false;
	return true;
}
bool jsval_to_std_map_string_string(JSContext *cx, jsval v, std::map<std::string, std::string>* ret)
{
    if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
    {
        return true;
    }
    
    JSObject* tmp = JSVAL_TO_OBJECT(v);
    if (!tmp) {
        CCLOG("%s", "jsval_to_ccvaluemap: the jsval is not an object.");
        return false;
    }
    
    JSObject* it = JS_NewPropertyIterator(cx, tmp);
    
    while (true)
    {
        jsid idp;
        jsval key;
        if (! JS_NextProperty(cx, it, &idp) || ! JS_IdToValue(cx, idp, &key)) {
            return false; // error
        }
        
        if (key == JSVAL_VOID) {
            break; // end of iteration
        }
        
        if (!JSVAL_IS_STRING(key)) {
            continue; // ignore integer properties
        }
        
        JSStringWrapper keyWrapper(JSVAL_TO_STRING(key), cx);
        
        JS::RootedValue value(cx);
        JS_GetPropertyById(cx, tmp, idp, &value);
        if (value.isString())
        {
            JSStringWrapper valueWapper(JSVAL_TO_STRING(value), cx);
            ret->insert(std::make_pair(keyWrapper.get(), valueWapper.get()));
        }
        else
        {
            CCASSERT(false, "not a string");
        }
    }
        
    return true;
}
예제 #9
0
bool
CallbackInterface::GetCallableProperty(JSContext* cx, JS::Handle<jsid> aPropId,
                                       JS::MutableHandle<JS::Value> aCallable)
{
  if (!JS_GetPropertyById(cx, CallbackKnownNotGray(), aPropId, aCallable)) {
    return false;
  }
  if (!aCallable.isObject() ||
      !JS::IsCallable(&aCallable.toObject())) {
    char* propName =
      JS_EncodeString(cx, JS_FORGET_STRING_FLATNESS(JSID_TO_FLAT_STRING(aPropId)));
    nsPrintfCString description("Property '%s'", propName);
    JS_free(cx, propName);
    ThrowErrorMessage(cx, MSG_NOT_CALLABLE, description.get());
    return false;
  }

  return true;
}
예제 #10
0
파일: importer.cpp 프로젝트: brownsr/cjs
static JSObject *
load_module_init(JSContext  *context,
                 JSObject   *in_object,
                 const char *full_path)
{
    JSObject *module_obj;
    JSBool found;
    jsid module_init_name;
    GFile *file;

    /* First we check if js module has already been loaded  */
    module_init_name = gjs_context_get_const_string(context, GJS_STRING_MODULE_INIT);
    if (!is_extension_module (full_path) &&
        JS_HasPropertyById(context, in_object, module_init_name, &found) && found) {
        jsval module_obj_val;

        if (JS_GetPropertyById(context,
                               in_object,
                               module_init_name,
                               &module_obj_val)) {
            return JSVAL_TO_OBJECT(module_obj_val);
        }
    }

    module_obj = create_module_object (context);
    file = g_file_new_for_commandline_arg(full_path);
    if (!import_file (context, "__init__", file, module_obj))
        goto out;

    if (!JS_DefinePropertyById(context, in_object,
                               module_init_name, OBJECT_TO_JSVAL(module_obj),
                               NULL, NULL,
                               GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT))
        goto out;

 out:
    g_object_unref (file);
    return module_obj;
}
void
XPCWrappedNativeScope::SetGlobal(XPCCallContext& ccx, JSObject* aGlobal)
{
    // We allow for calling this more than once. This feature is used by
    // nsXPConnect::InitClassesWithNewWrappedGlobal.

    mGlobalJSObject = aGlobal;
    mScriptObjectPrincipal = nsnull;
    // Now init our script object principal, if the new global has one

    const JSClass* jsClass = aGlobal->getJSClass();
    if(!(~jsClass->flags & (JSCLASS_HAS_PRIVATE |
                            JSCLASS_PRIVATE_IS_NSISUPPORTS)))
    {
        // Our global has an nsISupports native pointer.  Let's
        // see whether it's what we want.
        nsISupports* priv = (nsISupports*)xpc_GetJSPrivate(aGlobal);
        nsCOMPtr<nsIXPConnectWrappedNative> native =
            do_QueryInterface(priv);
        nsCOMPtr<nsIScriptObjectPrincipal> sop;
        if(native)
        {
            sop = do_QueryWrappedNative(native);
        }
        if(!sop)
        {
            sop = do_QueryInterface(priv);
        }
        mScriptObjectPrincipal = sop;
    }

    // Lookup 'globalObject.Object.prototype' for our wrapper's proto
    {
        AutoJSErrorAndExceptionEater eater(ccx); // scoped error eater

        jsval val;
        jsid idObj = mRuntime->GetStringID(XPCJSRuntime::IDX_OBJECT);
        jsid idFun = mRuntime->GetStringID(XPCJSRuntime::IDX_FUNCTION);
        jsid idProto = mRuntime->GetStringID(XPCJSRuntime::IDX_PROTOTYPE);

        if(JS_GetPropertyById(ccx, aGlobal, idObj, &val) &&
           !JSVAL_IS_PRIMITIVE(val) &&
           JS_GetPropertyById(ccx, JSVAL_TO_OBJECT(val), idProto, &val) &&
           !JSVAL_IS_PRIMITIVE(val))
        {
            mPrototypeJSObject = JSVAL_TO_OBJECT(val);
        }
        else
        {
            NS_ERROR("Can't get globalObject.Object.prototype");
        }

        if(JS_GetPropertyById(ccx, aGlobal, idFun, &val) &&
           !JSVAL_IS_PRIMITIVE(val) &&
           JS_GetPropertyById(ccx, JSVAL_TO_OBJECT(val), idProto, &val) &&
           !JSVAL_IS_PRIMITIVE(val))
        {
            mPrototypeJSFunction = JSVAL_TO_OBJECT(val);
        }
        else
        {
            NS_ERROR("Can't get globalObject.Function.prototype");
        }
    }

    // Clear the no helper wrapper prototype object so that a new one
    // gets created if needed.
    mPrototypeNoHelper = nsnull;
}
예제 #12
0
파일: vm.c 프로젝트: alepharchives/emonk
ENTERM
vm_call(JSContext* cx, JSObject* gl, job_ptr job)
{
    ENTERM resp;
    ENTERM head;
    ENTERM tail;
    jsval func;
    jsval args[256];
    jsval rval;
    jsid idp;
    int argc;
    
    // Get the function object.
    
    func = to_js(job->env, cx, job->name);
    if(func == JSVAL_VOID)
    {
        resp = vm_mk_error(job->env, util_mk_atom(job->env, "invalid_name"));
        goto send;
    }

    if(!JS_ValueToId(cx, func, &idp))
    {
        resp = vm_mk_error(job->env, util_mk_atom(job->env, "internal_error"));
        goto send;
    }
    
    if(!JS_GetPropertyById(cx, gl, idp, &func))
    {
        resp = vm_mk_error(job->env, util_mk_atom(job->env, "bad_property"));
        goto send;
    }

    if(JS_TypeOfValue(cx, func) != JSTYPE_FUNCTION)
    {
        resp = vm_mk_error(job->env, util_mk_atom(job->env, "not_a_function"));
        goto send;
    }

    // Creating function arguments.
    
    if(enif_is_empty_list(job->env, job->args))
    {
        argc = 0;
    }
    else
    {
        if(!enif_get_list_cell(job->env, job->args, &head, &tail))
        {
            resp = vm_mk_error(job->env, util_mk_atom(job->env, "invalid_argv"));
            goto send;
        }

        argc = 0;
        do {
            args[argc++] = to_js(job->env, cx, head);
        } while(enif_get_list_cell(job->env, tail, &head, &tail) && argc < 256);
    }

    // Call function
    if(!JS_CallFunctionValue(cx, gl, func, argc, args, &rval))
    {
        if(job->error != 0)
        {
            resp = vm_mk_error(job->env, job->error);
        }
        else
        {
            resp = vm_mk_error(job->env, util_mk_atom(job->env, "unknown"));
        }
    }
    else
    {
        resp = vm_mk_ok(job->env, to_erl(job->env, cx, rval));
    }

send:
    return enif_make_tuple2(job->env, job->ref, resp);
}
예제 #13
0
	// Clone a new value (and root it and add it to the mapping)
	jsval Clone(jsval val)
	{
		if (JSVAL_IS_DOUBLE(val))
		{
			jsval rval;
			CLONE_REQUIRE(JS_NewNumberValue(cxTo, JSVAL_TO_DOUBLE(val), &rval), L"JS_NewNumberValue");
			m_RooterTo.Push(rval);
			return rval;
		}

		if (JSVAL_IS_STRING(val))
		{
			size_t len;
			const jschar* chars = JS_GetStringCharsAndLength(cxFrom, JSVAL_TO_STRING(val), &len);
			CLONE_REQUIRE(chars, L"JS_GetStringCharsAndLength");
			JSString* str = JS_NewUCStringCopyN(cxTo, chars, len);
			CLONE_REQUIRE(str, L"JS_NewUCStringCopyN");
			jsval rval = STRING_TO_JSVAL(str);
			m_Mapping[JSVAL_TO_GCTHING(val)] = rval;
			m_RooterTo.Push(rval);
			return rval;
		}

		ENSURE(JSVAL_IS_OBJECT(val));

		JSObject* newObj;
		if (JS_IsArrayObject(cxFrom, JSVAL_TO_OBJECT(val)))
		{
			jsuint length;
			CLONE_REQUIRE(JS_GetArrayLength(cxFrom, JSVAL_TO_OBJECT(val), &length), L"JS_GetArrayLength");
			newObj = JS_NewArrayObject(cxTo, length, NULL);
			CLONE_REQUIRE(newObj, L"JS_NewArrayObject");
		}
		else
		{
			newObj = JS_NewObject(cxTo, NULL, NULL, NULL);
			CLONE_REQUIRE(newObj, L"JS_NewObject");
		}

		m_Mapping[JSVAL_TO_GCTHING(val)] = OBJECT_TO_JSVAL(newObj);
		m_RooterTo.Push(newObj);

		AutoJSIdArray ida (cxFrom, JS_Enumerate(cxFrom, JSVAL_TO_OBJECT(val)));
		CLONE_REQUIRE(ida.get(), L"JS_Enumerate");

		AutoGCRooter idaRooter(scriptInterfaceFrom);
		idaRooter.Push(ida.get());

		for (size_t i = 0; i < ida.length(); ++i)
		{
			jsid id = ida[i];
			jsval idval, propval;
			CLONE_REQUIRE(JS_IdToValue(cxFrom, id, &idval), L"JS_IdToValue");
			CLONE_REQUIRE(JS_GetPropertyById(cxFrom, JSVAL_TO_OBJECT(val), id, &propval), L"JS_GetPropertyById");
			jsval newPropval = GetOrClone(propval);

			if (JSVAL_IS_INT(idval))
			{
				// int jsids are portable across runtimes
				CLONE_REQUIRE(JS_SetPropertyById(cxTo, newObj, id, &newPropval), L"JS_SetPropertyById");
			}
			else if (JSVAL_IS_STRING(idval))
			{
				// string jsids are runtime-specific, so we need to copy the string content
				JSString* idstr = JS_ValueToString(cxFrom, idval);
				CLONE_REQUIRE(idstr, L"JS_ValueToString (id)");
				size_t len;
				const jschar* chars = JS_GetStringCharsAndLength(cxFrom, idstr, &len);
				CLONE_REQUIRE(idstr, L"JS_GetStringCharsAndLength (id)");
				CLONE_REQUIRE(JS_SetUCProperty(cxTo, newObj, chars, len, &newPropval), L"JS_SetUCProperty");
			}
			else
			{
				// this apparently could be an XML object; ignore it
			}
		}

		return OBJECT_TO_JSVAL(newObj);
	}
예제 #14
0
파일: importer.c 프로젝트: PofigNaNik/gjs
static JSObject *
load_module_init(JSContext  *context,
                 JSObject   *in_object,
                 const char *full_path)
{
    char *script;
    gsize script_len;
    jsval script_retval;
    JSObject *module_obj;
    GError *error;
    JSBool found;
    jsid module_init_name;

    /* First we check if js module has already been loaded  */
    module_init_name = gjs_runtime_get_const_string(JS_GetRuntime(context),
                                                    GJS_STRING_MODULE_INIT);
    if (JS_HasPropertyById(context, in_object, module_init_name, &found) && found) {
        jsval module_obj_val;

        if (JS_GetPropertyById(context,
                               in_object,
                               module_init_name,
                               &module_obj_val)) {
            return JSVAL_TO_OBJECT(module_obj_val);
        }
    }

    module_obj = JS_NewObject(context, NULL, NULL, NULL);
    if (module_obj == NULL) {
        return JS_FALSE;
    }

    /* https://bugzilla.mozilla.org/show_bug.cgi?id=599651 means we
     * can't just pass in the global as the parent */
    JS_SetParent(context, module_obj,
                 gjs_get_import_global (context));

    /* Define module in importer for future use and to avoid module_obj
     * object to be garbage collected during the evaluation of the script */
    JS_DefinePropertyById(context, in_object,
                          module_init_name, OBJECT_TO_JSVAL(module_obj),
                          NULL, NULL,
                          GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT);

    script_len = 0;
    error = NULL;

    if (!g_file_get_contents(full_path, &script, &script_len, &error)) {
        if (!g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_ISDIR) &&
            !g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOTDIR) &&
            !g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
            gjs_throw_g_error(context, error);
        else
            g_error_free(error);

        return NULL;
    }

    g_assert(script != NULL);

    gjs_debug(GJS_DEBUG_IMPORTER, "Importing %s", full_path);

    if (!JS_EvaluateScript(context,
                           module_obj,
                           script,
                           script_len,
                           full_path,
                           1, /* line number */
                           &script_retval)) {
        g_free(script);

        /* If JSOPTION_DONT_REPORT_UNCAUGHT is set then the exception
         * would be left set after the evaluate and not go to the error
         * reporter function.
         */
        if (JS_IsExceptionPending(context)) {
            gjs_debug(GJS_DEBUG_IMPORTER,
                      "Module " MODULE_INIT_FILENAME " left an exception set");
            gjs_log_and_keep_exception(context);
        } else {
            gjs_throw(context,
                      "JS_EvaluateScript() returned FALSE but did not set exception");
        }

        return NULL;
    }

    g_free(script);

    return module_obj;
}
static JSBool
XPC_XOW_GetOrSetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp,
                         JSBool isSet)
{
  if (id == GetRTStringByIndex(cx, XPCJSRuntime::IDX_TO_STRING)) {
    return JS_TRUE;
  }

  // Don't do anything if we already resolved to a wrapped function in
  // NewResolve. In practice, this means that this is a wrapped eval
  // function.
  jsval v = *vp;
  if (!JSVAL_IS_PRIMITIVE(v) &&
      JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(v)) &&
      JS_GetFunctionNative(cx, JS_ValueToFunction(cx, v)) ==
      XPC_XOW_FunctionWrapper) {
    return JS_TRUE;
  }

  JSObject *origObj = obj;
  obj = GetWrapper(obj);
  if (!obj) {
    return ThrowException(NS_ERROR_ILLEGAL_VALUE, cx);
  }

  XPCCallContext ccx(JS_CALLER, cx);
  if (!ccx.IsValid()) {
    return ThrowException(NS_ERROR_FAILURE, cx);
  }

  AUTO_MARK_JSVAL(ccx, vp);

  JSObject *wrappedObj = GetWrappedObject(cx, obj);
  if (!wrappedObj) {
    return ThrowException(NS_ERROR_ILLEGAL_VALUE, cx);
  }

  JSBool privilegeEnabled;
  nsresult rv = CanAccessWrapper(cx, wrappedObj, &privilegeEnabled);
  if (NS_FAILED(rv)) {
    if (rv != NS_ERROR_DOM_PROP_ACCESS_DENIED) {
      return JS_FALSE;
    }

    // This is a request to get a property across origins. We need to
    // determine if this property is allAccess. If it is, then we need to
    // actually get the property. If not, we simply need to throw an
    // exception.

    XPCWrappedNative *wn =
      XPCWrappedNative::GetWrappedNativeOfJSObject(cx, wrappedObj);
    NS_ASSERTION(wn, "How did we wrap a non-WrappedNative?");
    if (!IsValFrame(wrappedObj, id, wn)) {
      nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager();
      if (!ssm) {
        return ThrowException(NS_ERROR_NOT_INITIALIZED, cx);
      }
      rv = ssm->CheckPropertyAccess(cx, wrappedObj,
                                    STOBJ_GET_CLASS(wrappedObj)->name,
                                    id, isSet ? XPCWrapper::sSecMgrSetProp
                                              : XPCWrapper::sSecMgrGetProp);
      if (NS_FAILED(rv)) {
        // The security manager threw an exception for us.
        return JS_FALSE;
      }
    }

    return XPCWrapper::GetOrSetNativeProperty(cx, obj, wn, id, vp, isSet,
                                              JS_FALSE);
  }

  JSObject *proto = nsnull; // Initialize this to quiet GCC.
  JSBool checkProto =
    (isSet && id == GetRTStringByIndex(cx, XPCJSRuntime::IDX_PROTO));
  if (checkProto) {
    proto = STOBJ_GET_PROTO(wrappedObj);
  }

  // Same origin, pass this request along as though nothing interesting
  // happened.
  jsid asId;

  if (!JS_ValueToId(cx, id, &asId)) {
    return JS_FALSE;
  }

  JSBool ok = isSet
              ? JS_SetPropertyById(cx, wrappedObj, asId, vp)
              : JS_GetPropertyById(cx, wrappedObj, asId, vp);
  if (!ok) {
    return JS_FALSE;
  }

  if (checkProto) {
    JSObject *newProto = STOBJ_GET_PROTO(wrappedObj);

    // If code is trying to set obj.__proto__ and we're on obj's
    // prototype chain, then the JS_GetPropertyById above will do the
    // wrong thing if wrappedObj still delegates to Object.prototype.
    // However, it's hard to figure out if wrappedObj still does
    // delegate to Object.prototype so check to see if proto changed as a
    // result of setting __proto__.

    if (origObj != obj) {
      // Undo the damage.
      if (!JS_SetPrototype(cx, wrappedObj, proto) ||
          !JS_SetPrototype(cx, origObj, newProto)) {
        return JS_FALSE;
      }
    } else if (newProto) {
      // __proto__ setting is a bad hack, people shouldn't do it. In
      // this case we're setting the direct prototype of a XOW object,
      // in the interests of sanity only allow it to be set to null in
      // this case.

      JS_SetPrototype(cx, wrappedObj, proto);
      JS_ReportError(cx, "invalid __proto__ value (can only be set to null)");
      return JS_FALSE;
    }
  }

  return WrapSameOriginProp(cx, obj, vp);
}
예제 #16
0
static JSBool
JO(JSContext *cx, jsval *vp, StringifyContext *scx)
{
    JSObject *obj = JSVAL_TO_OBJECT(*vp);

    if (!scx->cb.append('{'))
        return JS_FALSE;

    jsval vec[3] = {JSVAL_NULL, JSVAL_NULL, JSVAL_NULL};
    JSAutoTempValueRooter tvr(cx, 3, vec);
    jsval& key = vec[0];
    jsval& outputValue = vec[1];

    JSObject *iterObj = NULL;
    jsval *keySource = vp;
    bool usingWhitelist = false;

    // if the replacer is an array, we use the keys from it
    if (scx->replacer && JS_IsArrayObject(cx, scx->replacer)) {
        usingWhitelist = true;
        vec[2] = OBJECT_TO_JSVAL(scx->replacer);
        keySource = &vec[2];
    }

    if (!js_ValueToIterator(cx, JSITER_ENUMERATE, keySource))
        return JS_FALSE;
    iterObj = JSVAL_TO_OBJECT(*keySource);

    JSBool memberWritten = JS_FALSE;

    bool ok = false;
    while (true) {
        outputValue = JSVAL_VOID;
        if (!js_CallIteratorNext(cx, iterObj, &key))
            goto error_break;
        if (key == JSVAL_HOLE)
            break;

        jsuint index = 0;
        if (usingWhitelist) {
            // skip non-index properties
            if (!js_IdIsIndex(key, &index))
                continue;

            jsval newKey;
            if (!scx->replacer->getProperty(cx, key, &newKey))
                goto error_break;
            key = newKey;
        }

        JSString *ks;
        if (JSVAL_IS_STRING(key)) {
            ks = JSVAL_TO_STRING(key);
        } else {
            ks = js_ValueToString(cx, key);
            if (!ks)
                goto error_break;
        }
        JSAutoTempValueRooter keyStringRoot(cx, ks);

        // Don't include prototype properties, since this operation is
        // supposed to be implemented as if by ES3.1 Object.keys()
        jsid id;
        jsval v = JS_FALSE;
        if (!js_ValueToStringId(cx, STRING_TO_JSVAL(ks), &id) ||
            !js_HasOwnProperty(cx, obj->map->ops->lookupProperty, obj, id, &v)) {
            goto error_break;
        }

        if (v != JSVAL_TRUE)
            continue;

        if (!JS_GetPropertyById(cx, obj, id, &outputValue))
            goto error_break;

        if (JSVAL_IS_OBJECT(outputValue) && !js_TryJSON(cx, &outputValue))
            goto error_break;

        // call this here, so we don't write out keys if the replacer function
        // wants to elide the value.
        if (!CallReplacerFunction(cx, id, obj, scx, &outputValue))
            goto error_break;

        JSType type = JS_TypeOfValue(cx, outputValue);

        // elide undefined values and functions and XML
        if (outputValue == JSVAL_VOID || type == JSTYPE_FUNCTION || type == JSTYPE_XML)
            continue;

        // output a comma unless this is the first member to write
        if (memberWritten && !scx->cb.append(','))
            goto error_break;
        memberWritten = JS_TRUE;

        if (!WriteIndent(cx, scx, scx->depth))
            goto error_break;

        // Be careful below, this string is weakly rooted
        JSString *s = js_ValueToString(cx, key);
        if (!s)
            goto error_break;

        const jschar *chars;
        size_t length;
        s->getCharsAndLength(chars, length);
        if (!write_string(cx, scx->cb, chars, length) ||
            !scx->cb.append(':') ||
            !Str(cx, id, obj, scx, &outputValue, false)) {
            goto error_break;
        }
    }
    ok = true;

  error_break:
    if (iterObj) {
        // Always close the iterator, but make sure not to stomp on OK
        JS_ASSERT(OBJECT_TO_JSVAL(iterObj) == *keySource);
        ok &= js_CloseIterator(cx, *keySource);
    }

    if (!ok)
        return JS_FALSE;

    if (memberWritten && !WriteIndent(cx, scx, scx->depth - 1))
        return JS_FALSE;

    return scx->cb.append('}');
}
예제 #17
0
void CBinarySerializerScriptImpl::HandleScriptVal(JS::HandleValue val)
{
	JSContext* cx = m_ScriptInterface.GetContext();
	JSAutoRequest rq(cx);

	switch (JS_TypeOfValue(cx, val))
	{
	case JSTYPE_VOID:
	{
		m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_VOID);
		break;
	}
	case JSTYPE_NULL: // This type is never actually returned (it's a JS2 feature)
	{
		m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_NULL);
		break;
	}
	case JSTYPE_OBJECT:
	{
		if (val.isNull())
		{
			m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_NULL);
			break;
		}

		JS::RootedObject obj(cx, &val.toObject());

		// If we've already serialized this object, just output a reference to it
		u32 tag = GetScriptBackrefTag(obj);
		if (tag)
		{
			m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_BACKREF);
			m_Serializer.NumberU32_Unbounded("tag", tag);
			break;
		}

		// Arrays are special cases of Object
		if (JS_IsArrayObject(cx, obj))
		{
			m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_ARRAY);
			// TODO: probably should have a more efficient storage format

			// Arrays like [1, 2, ] have an 'undefined' at the end which is part of the
			// length but seemingly isn't enumerated, so store the length explicitly
			uint length = 0;
			if (!JS_GetArrayLength(cx, obj, &length))
				throw PSERROR_Serialize_ScriptError("JS_GetArrayLength failed");
			m_Serializer.NumberU32_Unbounded("array length", length);
		}
		else if (JS_IsTypedArrayObject(obj))
		{
			m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_TYPED_ARRAY);

			m_Serializer.NumberU8_Unbounded("array type", GetArrayType(JS_GetArrayBufferViewType(obj)));
			m_Serializer.NumberU32_Unbounded("byte offset", JS_GetTypedArrayByteOffset(obj));
			m_Serializer.NumberU32_Unbounded("length", JS_GetTypedArrayLength(obj));

			// Now handle its array buffer
			// this may be a backref, since ArrayBuffers can be shared by multiple views
			JS::RootedValue bufferVal(cx, JS::ObjectValue(*JS_GetArrayBufferViewBuffer(cx, obj)));
			HandleScriptVal(bufferVal);
			break;
		}
		else if (JS_IsArrayBufferObject(obj))
		{
			m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_ARRAY_BUFFER);

#if BYTE_ORDER != LITTLE_ENDIAN
#error TODO: need to convert JS ArrayBuffer data to little-endian
#endif

			u32 length = JS_GetArrayBufferByteLength(obj);
			m_Serializer.NumberU32_Unbounded("buffer length", length);
			JS::AutoCheckCannotGC nogc;
			m_Serializer.RawBytes("buffer data", (const u8*)JS_GetArrayBufferData(obj, nogc), length);
			break;
		}
		else
		{
			// Find type of object
			const JSClass* jsclass = JS_GetClass(obj);
			if (!jsclass)
				throw PSERROR_Serialize_ScriptError("JS_GetClass failed");
// TODO: Remove this workaround for upstream API breakage when updating SpiderMonkey
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1236373
#define JSCLASS_CACHED_PROTO_WIDTH js::JSCLASS_CACHED_PROTO_WIDTH
			JSProtoKey protokey = JSCLASS_CACHED_PROTO_KEY(jsclass);
#undef JSCLASS_CACHED_PROTO_WIDTH

			if (protokey == JSProto_Object)
			{
				// Object class - check for user-defined prototype
				JS::RootedObject proto(cx);
				JS_GetPrototype(cx, obj, &proto);
				if (!proto)
					throw PSERROR_Serialize_ScriptError("JS_GetPrototype failed");

				if (m_SerializablePrototypes->empty() || !IsSerializablePrototype(proto))
				{
					// Standard Object prototype
					m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT);

					// TODO: maybe we should throw an error for unrecognized non-Object prototypes?
					//	(requires fixing AI serialization first and excluding component scripts)
				}
				else
				{
					// User-defined custom prototype
					m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_PROTOTYPE);

					const std::wstring prototypeName = GetPrototypeName(proto);
					m_Serializer.String("proto name", prototypeName, 0, 256);

					// Does it have custom Serialize function?
					// if so, we serialize the data it returns, rather than the object's properties directly
					bool hasCustomSerialize;
					if (!JS_HasProperty(cx, obj, "Serialize", &hasCustomSerialize))
						throw PSERROR_Serialize_ScriptError("JS_HasProperty failed");

					if (hasCustomSerialize)
					{
						JS::RootedValue serialize(cx);
						if (!JS_GetProperty(cx, obj, "Serialize", &serialize))
							throw PSERROR_Serialize_ScriptError("JS_GetProperty failed");

						// If serialize is null, so don't serialize anything more
						if (!serialize.isNull())
						{
							JS::RootedValue data(cx);
							if (!m_ScriptInterface.CallFunction(val, "Serialize", &data))
								throw PSERROR_Serialize_ScriptError("Prototype Serialize function failed");
							HandleScriptVal(data);
						}
						break;
					}
				}
			}
			else if (protokey == JSProto_Number)
			{
				// Standard Number object
				m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_NUMBER);
				// Get primitive value
				double d;
				if (!JS::ToNumber(cx, val, &d))
					throw PSERROR_Serialize_ScriptError("JS::ToNumber failed");
				m_Serializer.NumberDouble_Unbounded("value", d);
				break;
			}
			else if (protokey == JSProto_String)
			{
				// Standard String object
				m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_STRING);
				// Get primitive value
				JS::RootedString str(cx, JS::ToString(cx, val));
				if (!str)
					throw PSERROR_Serialize_ScriptError("JS_ValueToString failed");
				ScriptString("value", str);
				break;
			}
			else if (protokey == JSProto_Boolean)
			{
				// Standard Boolean object
				m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_BOOLEAN);
				// Get primitive value
				bool b = JS::ToBoolean(val);
				m_Serializer.Bool("value", b);
				break;
			}
			// TODO: Follow upstream progresses about a JS::IsMapObject
			// https://bugzilla.mozilla.org/show_bug.cgi?id=1285909
			else if (protokey == JSProto_Map)
			{
				m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_MAP);
				m_Serializer.NumberU32_Unbounded("map size", JS::MapSize(cx, obj));

				JS::RootedValue keyValueIterator(cx);
				if (!JS::MapEntries(cx, obj, &keyValueIterator))
					throw PSERROR_Serialize_ScriptError("JS::MapEntries failed");

				JS::ForOfIterator it(cx);
				if (!it.init(keyValueIterator))
					throw PSERROR_Serialize_ScriptError("JS::ForOfIterator::init failed");

				JS::RootedValue keyValuePair(cx);
				bool done;
				while (true)
				{
					if (!it.next(&keyValuePair, &done))
						throw PSERROR_Serialize_ScriptError("JS::ForOfIterator::next failed");

					if (done)
						break;

					JS::RootedObject keyValuePairObj(cx, &keyValuePair.toObject());
					JS::RootedValue key(cx);
					JS::RootedValue value(cx);
					ENSURE(JS_GetElement(cx, keyValuePairObj, 0, &key));
					ENSURE(JS_GetElement(cx, keyValuePairObj, 1, &value));

					HandleScriptVal(key);
					HandleScriptVal(value);
				}
				break;
			}
			// TODO: Follow upstream progresses about a JS::IsSetObject
			// https://bugzilla.mozilla.org/show_bug.cgi?id=1285909
			else if (protokey == JSProto_Set)
			{
				// TODO: When updating SpiderMonkey to a release after 38 use the C++ API for Sets.
				// https://bugzilla.mozilla.org/show_bug.cgi?id=1159469
				u32 setSize;
				m_ScriptInterface.GetProperty(val, "size", setSize);

				m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_SET);
				m_Serializer.NumberU32_Unbounded("set size", setSize);

				JS::RootedValue valueIterator(cx);
				m_ScriptInterface.CallFunction(val, "values", &valueIterator);
				for (u32 i=0; i<setSize; ++i)
				{
					JS::RootedValue currentIterator(cx);
					JS::RootedValue value(cx);
					ENSURE(m_ScriptInterface.CallFunction(valueIterator, "next", &currentIterator));

					m_ScriptInterface.GetProperty(currentIterator, "value", &value);

					HandleScriptVal(value);
				}

				break;
			}
			else
			{
				// Unrecognized class
				LOGERROR("Cannot serialise JS objects with unrecognized class '%s'", jsclass->name);
				throw PSERROR_Serialize_InvalidScriptValue();
			}
		}

		// Find all properties (ordered by insertion time)
		JS::AutoIdArray ida (cx, JS_Enumerate(cx, obj));
		if (!ida)
			throw PSERROR_Serialize_ScriptError("JS_Enumerate failed");

		m_Serializer.NumberU32_Unbounded("num props", (u32)ida.length());

		for (size_t i = 0; i < ida.length(); ++i)
		{
			JS::RootedId id(cx, ida[i]);

			JS::RootedValue idval(cx);
			JS::RootedValue propval(cx);

			// Forbid getters, which might delete values and mess things up.
			JS::Rooted<JSPropertyDescriptor> desc(cx);
			if (!JS_GetPropertyDescriptorById(cx, obj, id, &desc))
				throw PSERROR_Serialize_ScriptError("JS_GetPropertyDescriptorById failed");
			if (desc.hasGetterObject())
				throw PSERROR_Serialize_ScriptError("Cannot serialize property getters");

			// Get the property name as a string
			if (!JS_IdToValue(cx, id, &idval))
				throw PSERROR_Serialize_ScriptError("JS_IdToValue failed");
			JS::RootedString idstr(cx, JS::ToString(cx, idval));
			if (!idstr)
				throw PSERROR_Serialize_ScriptError("JS_ValueToString failed");

			ScriptString("prop name", idstr);

			if (!JS_GetPropertyById(cx, obj, id, &propval))
				throw PSERROR_Serialize_ScriptError("JS_GetPropertyById failed");

			HandleScriptVal(propval);
		}

		break;
	}
	case JSTYPE_FUNCTION:
	{
		// We can't serialise functions, but we can at least name the offender (hopefully)
		std::wstring funcname(L"(unnamed)");
		JS::RootedFunction func(cx, JS_ValueToFunction(cx, val));
		if (func)
		{
			JS::RootedString string(cx, JS_GetFunctionId(func));
			if (string)
			{
				if (JS_StringHasLatin1Chars(string))
				{
					size_t length;
					JS::AutoCheckCannotGC nogc;
					const JS::Latin1Char* ch = JS_GetLatin1StringCharsAndLength(cx, nogc, string, &length);
					if (ch && length > 0)
						funcname.assign(ch, ch + length);
				}
				else
				{
					size_t length;
					JS::AutoCheckCannotGC nogc;
					const char16_t* ch = JS_GetTwoByteStringCharsAndLength(cx, nogc, string, &length);
					if (ch && length > 0)
						funcname.assign(ch, ch + length);
				}
			}
		}

		LOGERROR("Cannot serialise JS objects of type 'function': %s", utf8_from_wstring(funcname));
		throw PSERROR_Serialize_InvalidScriptValue();
	}
	case JSTYPE_STRING:
	{
		m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_STRING);
		JS::RootedString stringVal(cx, val.toString());
		ScriptString("string", stringVal);
		break;
	}
	case JSTYPE_NUMBER:
	{
		// To reduce the size of the serialized data, we handle integers and doubles separately.
		// We can't check for val.isInt32 and val.isDouble directly, because integer numbers are not guaranteed
		// to be represented as integers. A number like 33 could be stored as integer on the computer of one player
		// and as double on the other player's computer. That would cause out of sync errors in multiplayer games because
		// their binary representation and thus the hash would be different.

		double d;
		d = val.toNumber();
		i32 integer;

		if (JS_DoubleIsInt32(d, &integer))
		{
			m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_INT);
			m_Serializer.NumberI32_Unbounded("value", integer);
		}
		else
		{
			m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_DOUBLE);
			m_Serializer.NumberDouble_Unbounded("value", d);
		}
		break;
	}
	case JSTYPE_BOOLEAN:
	{
		m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_BOOLEAN);
		bool b = val.toBoolean();
		m_Serializer.NumberU8_Unbounded("value", b ? 1 : 0);
		break;
	}
	default:
	{
		debug_warn(L"Invalid TypeOfValue");
		throw PSERROR_Serialize_InvalidScriptValue();
	}
	}
}
예제 #18
0
// Convert from a jsval to an AtNode
static AtSmartPtr<AtNode> ConvertNode(JSContext* cx, jsval node)
{
	AtSmartPtr<AtNode> obj (new AtNode());

	// Non-objects get converted into strings
	if (!JSVAL_IS_OBJECT(node))
	{
		JSString* str = JS_ValueToString(cx, node);
		if (!str)
			return obj; // error
		size_t valueLen;
		const jschar* valueChars = JS_GetStringCharsAndLength(cx, str, &valueLen);
		if (!valueChars)
			return obj; // error
		wxString valueWx(reinterpret_cast<const char*>(valueChars), wxMBConvUTF16(), valueLen*2);

		obj->value = valueWx.c_str();

		// Annotate numbers/booleans specially, to allow round-tripping
		if (JSVAL_IS_NUMBER(node))
		{
			obj->children.insert(AtNode::child_pairtype(
				"@number", AtSmartPtr<AtNode>(new AtNode())
			));
		}
		else if (JSVAL_IS_BOOLEAN(node))
		{
			obj->children.insert(AtNode::child_pairtype(
				"@boolean", AtSmartPtr<AtNode>(new AtNode())
			));
		}

		return obj;
	}

	JSObject* it = JS_NewPropertyIterator(cx, JSVAL_TO_OBJECT(node));
	if (!it)
		return obj; // error

	while (true)
	{
		jsid idp;
		jsval val;
		if (! JS_NextProperty(cx, it, &idp) || ! JS_IdToValue(cx, idp, &val))
			return obj; // error
		if (val == JSVAL_VOID)
			break; // end of iteration
		if (! JSVAL_IS_STRING(val))
			continue; // ignore integer properties

		JSString* name = JSVAL_TO_STRING(val);
		size_t len;
		const jschar* chars = JS_GetStringCharsAndLength(cx, name, &len);
		wxString nameWx(reinterpret_cast<const char*>(chars), wxMBConvUTF16(), len*2);
		std::string nameStr(nameWx.ToUTF8().data());

		jsval vp;
		if (!JS_GetPropertyById(cx, JSVAL_TO_OBJECT(node), idp, &vp))
			return obj; // error

		// Unwrap arrays into a special format like <$name><item>$i0</item><item>...
		// (This assumes arrays aren't nested)
		if (JSVAL_IS_OBJECT(vp) && JS_IsArrayObject(cx, JSVAL_TO_OBJECT(vp)))
		{
			AtSmartPtr<AtNode> child(new AtNode());
			child->children.insert(AtNode::child_pairtype(
				"@array", AtSmartPtr<AtNode>(new AtNode())
			));

			jsuint arrayLength;
			if (!JS_GetArrayLength(cx, JSVAL_TO_OBJECT(vp), &arrayLength))
				return obj; // error

			for (jsuint i = 0; i < arrayLength; ++i)
			{
				jsval val;
				if (!JS_GetElement(cx, JSVAL_TO_OBJECT(vp), i, &val))
					return obj; // error

				child->children.insert(AtNode::child_pairtype(
					"item", ConvertNode(cx, val)
				));
			}

			obj->children.insert(AtNode::child_pairtype(
				nameStr, child
			));
		}
		else
		{
			obj->children.insert(AtNode::child_pairtype(
				nameStr, ConvertNode(cx, vp)
			));
		}
	}

	return obj;
}
예제 #19
0
NS_IMETHODIMP
IDBDatabase::CreateObjectStore(const nsAString& aName,
                               const jsval& aOptions,
                               JSContext* aCx,
                               nsIIDBObjectStore** _retval)
{
  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");

  IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction();

  if (!transaction ||
      transaction->Mode() != nsIIDBTransaction::VERSION_CHANGE) {
    return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
  }

  DatabaseInfo* databaseInfo = transaction->DBInfo();

  nsString keyPath;
  keyPath.SetIsVoid(true);
  nsTArray<nsString> keyPathArray;
  bool autoIncrement = false;

  if (!JSVAL_IS_VOID(aOptions) && !JSVAL_IS_NULL(aOptions)) {
    if (JSVAL_IS_PRIMITIVE(aOptions)) {
      // XXX This isn't the right error
      return NS_ERROR_DOM_TYPE_ERR;
    }

    NS_ASSERTION(JSVAL_IS_OBJECT(aOptions), "Huh?!");
    JSObject* options = JSVAL_TO_OBJECT(aOptions);

    // Get keyPath
    jsval val;
    if (!JS_GetPropertyById(aCx, options, nsDOMClassInfo::sKeyPath_id, &val)) {
      NS_WARNING("JS_GetPropertyById failed!");
      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
    }

    if (!JSVAL_IS_VOID(val) && !JSVAL_IS_NULL(val)) {
      if (!JSVAL_IS_PRIMITIVE(val) &&
          JS_IsArrayObject(aCx, JSVAL_TO_OBJECT(val))) {
    
        JSObject* obj = JSVAL_TO_OBJECT(val);
    
        jsuint length;
        if (!JS_GetArrayLength(aCx, obj, &length)) {
          return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
        }
    
        if (!length) {
          return NS_ERROR_DOM_SYNTAX_ERR;
        }
    
        keyPathArray.SetCapacity(length);
    
        for (jsuint index = 0; index < length; index++) {
          jsval val;
          JSString* jsstr;
          nsDependentJSString str;
          if (!JS_GetElement(aCx, obj, index, &val) ||
              !(jsstr = JS_ValueToString(aCx, val)) ||
              !str.init(aCx, jsstr)) {
            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
          }
    
          if (!IDBObjectStore::IsValidKeyPath(aCx, str)) {
            return NS_ERROR_DOM_SYNTAX_ERR;
          }
    
          keyPathArray.AppendElement(str);
        }
    
        NS_ASSERTION(!keyPathArray.IsEmpty(), "This shouldn't have happened!");
      }
      else {
        JSString* jsstr;
        nsDependentJSString str;
        if (!(jsstr = JS_ValueToString(aCx, val)) ||
            !str.init(aCx, jsstr)) {
          return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
        }
    
        if (!IDBObjectStore::IsValidKeyPath(aCx, str)) {
          return NS_ERROR_DOM_SYNTAX_ERR;
        }
    
        keyPath = str;
      }
    }

    if (!JS_GetPropertyById(aCx, options, nsDOMClassInfo::sAutoIncrement_id,
                            &val)) {
      NS_WARNING("JS_GetPropertyById failed!");
      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
    }

    JSBool boolVal;
    if (!JS_ValueToBoolean(aCx, val, &boolVal)) {
      NS_WARNING("JS_ValueToBoolean failed!");
      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
    }
    autoIncrement = !!boolVal;
  }

  if (databaseInfo->ContainsStoreName(aName)) {
    return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
  }

  if (autoIncrement &&
      ((!keyPath.IsVoid() && keyPath.IsEmpty()) || !keyPathArray.IsEmpty())) {
    return NS_ERROR_DOM_INVALID_ACCESS_ERR;
  }

  nsRefPtr<ObjectStoreInfo> newInfo(new ObjectStoreInfo());

  newInfo->name = aName;
  newInfo->id = databaseInfo->nextObjectStoreId++;
  newInfo->keyPath = keyPath;
  newInfo->keyPathArray = keyPathArray;
  newInfo->nextAutoIncrementId = autoIncrement ? 1 : 0;
  newInfo->comittedAutoIncrementId = newInfo->nextAutoIncrementId;

  if (!databaseInfo->PutObjectStore(newInfo)) {
    NS_WARNING("Put failed!");
    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
  }

  // Don't leave this in the hash if we fail below!
  AutoRemoveObjectStore autoRemove(databaseInfo, aName);

  nsRefPtr<IDBObjectStore> objectStore =
    transaction->GetOrCreateObjectStore(aName, newInfo);
  NS_ENSURE_TRUE(objectStore, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);

  nsRefPtr<CreateObjectStoreHelper> helper =
    new CreateObjectStoreHelper(transaction, objectStore);

  nsresult rv = helper->DispatchToTransactionPool();
  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);

  autoRemove.forget();

  objectStore.forget(_retval);
  return NS_OK;
}