Пример #1
0
/*
 * Like JSEnumerateOp, but enum provides contextual information as follows:
 *
 * JSENUMERATE_INIT: allocate private enum struct in state_p, return number
 * of elements in *id_p
 * JSENUMERATE_NEXT: return next property id in *id_p, and if no new property
 * free state_p and set to JSVAL_NULL
 * JSENUMERATE_DESTROY : destroy state_p
 *
 * Note that in a for ... in loop, this will be called first on the object,
 * then on its prototype.
 *
 */
static JSBool
importer_new_enumerate(JSContext  *context,
                       JSObject  **object,
                       JSIterateOp enum_op,
                       jsval      *state_p,
                       jsid       *id_p)
{
    ImporterIterator *iter;

    switch (enum_op) {
    case JSENUMERATE_INIT_ALL:
    case JSENUMERATE_INIT: {
        Importer *priv;
        JSObject *search_path;
        jsval search_path_val;
        guint32 search_path_len;
        guint32 i;
        jsid search_path_name;

        if (state_p)
            *state_p = JSVAL_NULL;

        if (id_p)
            *id_p = INT_TO_JSID(0);

        priv = priv_from_js(context, *object);

        if (!priv)
            /* we are enumerating the prototype properties */
            return JS_TRUE;

        search_path_name = gjs_runtime_get_const_string(JS_GetRuntime(context),
                                                        GJS_STRING_SEARCH_PATH);
        if (!gjs_object_require_property(context, *object, "importer", search_path_name, &search_path_val))
            return JS_FALSE;

        if (!JSVAL_IS_OBJECT(search_path_val)) {
            gjs_throw(context, "searchPath property on importer is not an object");
            return JS_FALSE;
        }

        search_path = JSVAL_TO_OBJECT(search_path_val);

        if (!JS_IsArrayObject(context, search_path)) {
            gjs_throw(context, "searchPath property on importer is not an array");
            return JS_FALSE;
        }

        if (!JS_GetArrayLength(context, search_path, &search_path_len)) {
            gjs_throw(context, "searchPath array has no length");
            return JS_FALSE;
        }

        iter = importer_iterator_new();

        for (i = 0; i < search_path_len; ++i) {
            char *dirname = NULL;
            char *init_path;
            const char *filename;
            jsval elem;
            GDir *dir = NULL;

            elem = JSVAL_VOID;
            if (!JS_GetElement(context, search_path, i, &elem)) {
                /* this means there was an exception, while elem == JSVAL_VOID
                 * means no element found
                 */
                importer_iterator_free(iter);
                return JS_FALSE;
            }

            if (JSVAL_IS_VOID(elem))
                continue;

            if (!JSVAL_IS_STRING(elem)) {
                gjs_throw(context, "importer searchPath contains non-string");
                importer_iterator_free(iter);
                return JS_FALSE;
            }

            if (!gjs_string_to_utf8(context, elem, &dirname)) {
                importer_iterator_free(iter);
                return JS_FALSE; /* Error message already set */
            }

            init_path = g_build_filename(dirname, MODULE_INIT_FILENAME,
                                         NULL);

            load_module_elements(context, *object, iter, init_path);

            g_free(init_path);

            dir = g_dir_open(dirname, 0, NULL);

            if (!dir) {
                g_free(dirname);
                continue;
            }

            while ((filename = g_dir_read_name(dir))) {
                char *full_path;

                /* skip hidden files and directories (.svn, .git, ...) */
                if (filename[0] == '.')
                    continue;

                /* skip module init file */
                if (strcmp(filename, MODULE_INIT_FILENAME) == 0)
                    continue;

                full_path = g_build_filename(dirname, filename, NULL);

                if (g_file_test(full_path, G_FILE_TEST_IS_DIR)) {
                    g_ptr_array_add(iter->elements, g_strdup(filename));
                } else {
                    if (g_str_has_suffix(filename, "."G_MODULE_SUFFIX) ||
                        g_str_has_suffix(filename, ".js")) {
                        g_ptr_array_add(iter->elements,
                                        g_strndup(filename, strlen(filename) - 3));
                    }
                }

                g_free(full_path);
            }
            g_dir_close(dir);

            g_free(dirname);
        }

        if (state_p)
            *state_p = PRIVATE_TO_JSVAL(iter);

        if (id_p)
            *id_p = INT_TO_JSID(iter->elements->len);

        break;
    }

    case JSENUMERATE_NEXT: {
        jsval element_val;

        if (!state_p) {
            gjs_throw(context, "Enumerate with no iterator set?");
            return JS_FALSE;
        }

        if (JSVAL_IS_NULL(*state_p)) /* Iterating prototype */
            return JS_TRUE;

        iter = JSVAL_TO_PRIVATE(*state_p);

        if (iter->index < iter->elements->len) {
            if (!gjs_string_from_utf8(context,
                                         g_ptr_array_index(iter->elements,
                                                           iter->index++),
                                         -1,
                                         &element_val))
                return JS_FALSE;

            if (!JS_ValueToId(context, element_val, id_p))
                return JS_FALSE;

            break;
        }
        /* else fall through to destroying the iterator */
    }

    case JSENUMERATE_DESTROY: {
        if (state_p && !JSVAL_IS_NULL(*state_p)) {
            iter = JSVAL_TO_PRIVATE(*state_p);

            importer_iterator_free(iter);

            *state_p = JSVAL_NULL;
        }
    }
    }

    return JS_TRUE;
}
Пример #2
0
/*
 * Like JSEnumerateOp, but enum provides contextual information as follows:
 *
 * JSENUMERATE_INIT: allocate private enum struct in state_p, return number
 * of elements in *id_p
 * JSENUMERATE_NEXT: return next property id in *id_p, and if no new property
 * free state_p and set to JSVAL_NULL
 * JSENUMERATE_DESTROY : destroy state_p
 *
 * Note that in a for ... in loop, this will be called first on the object,
 * then on its prototype.
 *
 */
static JSBool
importer_new_enumerate(JSContext  *context,
                       JS::HandleObject object,
                       JSIterateOp enum_op,
                       JS::MutableHandleValue statep,
                       JS::MutableHandleId idp)
{
    ImporterIterator *iter;

    switch (enum_op) {
    case JSENUMERATE_INIT_ALL:
    case JSENUMERATE_INIT: {
        Importer *priv;
        JSObject *search_path;
        jsval search_path_val;
        uint32_t search_path_len;
        uint32_t i;
        jsid search_path_name;

        statep.set(JSVAL_NULL);

        idp.set(INT_TO_JSID(0));

        priv = priv_from_js(context, object);

        if (!priv)
            /* we are enumerating the prototype properties */
            return JS_TRUE;

        search_path_name = gjs_context_get_const_string(context, GJS_STRING_SEARCH_PATH);
        if (!gjs_object_require_property(context, object, "importer", search_path_name, &search_path_val))
            return JS_FALSE;

        if (!search_path_val.isObject()) {
            gjs_throw(context, "searchPath property on importer is not an object");
            return JS_FALSE;
        }

        search_path = JSVAL_TO_OBJECT(search_path_val);

        if (!JS_IsArrayObject(context, search_path)) {
            gjs_throw(context, "searchPath property on importer is not an array");
            return JS_FALSE;
        }

        if (!JS_GetArrayLength(context, search_path, &search_path_len)) {
            gjs_throw(context, "searchPath array has no length");
            return JS_FALSE;
        }

        iter = importer_iterator_new();

        for (i = 0; i < search_path_len; ++i) {
            std::string dirname;
            std::string init_path;
            const char *filename;
            jsval elem;
            std::vector<std::string> dir;

            elem = JSVAL_VOID;
            if (!JS_GetElement(context, search_path, i, &elem)) {
                /* this means there was an exception, while elem == JSVAL_VOID
                 * means no element found
                 */
                importer_iterator_free(iter);
                return JS_FALSE;
            }

            if (JSVAL_IS_VOID(elem))
                continue;

            if (!JSVAL_IS_STRING(elem)) {
                gjs_throw(context, "importer searchPath contains non-string");
                importer_iterator_free(iter);
                return JS_FALSE;
            }

            if (!gjs_string_to_utf8(context, elem, dirname)) {
                importer_iterator_free(iter);
                return JS_FALSE; /* Error message already set */
            }

            init_path = pathCombine(dirname, MODULE_INIT_FILENAME);

            load_module_elements(context, object, iter, init_path);

            dir = enumerateFilesInDirectory(dirname);

            if (dir.size() == 0) {
                continue;
            }

            for(auto filename : dir) {
                std::string full_path;

                /* skip hidden files and directories (.svn, .git, ...) */
                if (filename[0] == '.')
                    continue;

                /* skip module init file */
                if (filename == MODULE_INIT_FILENAME)
                    continue;

                full_path = pathCombine(dirname, filename);

                if (is_directory(full_path)) {
                    iter->elements.push_back(filename);
                } else {
                    if (filename.rfind(MODULE_SUFFIX) != std::string::npos ||
                        filename.rfind(JS_SUFFIX) != std::string::npos) {
                        iter->elements.push_back(filename.substr(0, filename.size()-3));
                    }
                }
            }
        }

        statep.set(PRIVATE_TO_JSVAL(iter));

        idp.set(INT_TO_JSID(iter->elements.size()));

        break;
    }

    case JSENUMERATE_NEXT: {
        jsval element_val;

        if (JSVAL_IS_NULL(statep)) /* Iterating prototype */
            return JS_TRUE;

        iter = (ImporterIterator*) JSVAL_TO_PRIVATE(statep);

        if (iter->index < iter->elements.size()) {
            if (!gjs_string_from_utf8(context,
                                         iter->elements.at(iter->index++),
                                         &element_val))
                return JS_FALSE;

            jsid id;
            if (!JS_ValueToId(context, element_val, &id))
                return JS_FALSE;
            idp.set(id);

            break;
        }
        /* else fall through to destroying the iterator */
    }

    case JSENUMERATE_DESTROY: {
        if (!JSVAL_IS_NULL(statep)) {
            iter = (ImporterIterator*) JSVAL_TO_PRIVATE(statep);

            importer_iterator_free(iter);

            statep.set(JSVAL_NULL);
        }
    }
    }

    return JS_TRUE;
}
Пример #3
0
JSBool
js_ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp)
{
    JSErrNum errorNumber;
    const JSErrorFormatString *errorString;
    JSExnType exn;
    jsval tv[4];
    JSTempValueRooter tvr;
    JSBool ok;
    JSObject *errProto, *errObject;
    JSString *messageStr, *filenameStr;

    /*
     * Tell our caller to report immediately if cx has no active frames, or if
     * this report is just a warning.
     */
    JS_ASSERT(reportp);
    if (!cx->fp || JSREPORT_IS_WARNING(reportp->flags))
        return JS_FALSE;

    /* Find the exception index associated with this error. */
    errorNumber = (JSErrNum) reportp->errorNumber;
    errorString = js_GetLocalizedErrorMessage(cx, NULL, NULL, errorNumber);
    exn = errorString ? (JSExnType) errorString->exnType : JSEXN_NONE;
    JS_ASSERT(exn < JSEXN_LIMIT);

#if defined( DEBUG_mccabe ) && defined ( PRINTNAMES )
    /* Print the error name and the associated exception name to stderr */
    fprintf(stderr, "%s\t%s\n",
            errortoexnname[errorNumber].name,
            errortoexnname[errorNumber].exception);
#endif

    /*
     * Return false (no exception raised) if no exception is associated
     * with the given error number.
     */
    if (exn == JSEXN_NONE)
        return JS_FALSE;

    /*
     * Prevent runaway recursion, via cx->generatingError.  If an out-of-memory
     * error occurs, no exception object will be created, but we don't assume
     * that OOM is the only kind of error that subroutines of this function
     * called below might raise.
     */
    if (cx->generatingError)
        return JS_FALSE;

    /* After this point the control must flow through the label out. */
    cx->generatingError = JS_TRUE;

    /* Protect the newly-created strings below from nesting GCs. */
    memset(tv, 0, sizeof tv);
    JS_PUSH_TEMP_ROOT(cx, JS_ARRAY_LENGTH(tv), tv, &tvr);

    /*
     * Try to get an appropriate prototype by looking up the corresponding
     * exception constructor name in the scope chain of the current context's
     * top stack frame, or in the global object if no frame is active.
     */
    ok = js_GetClassPrototype(cx, NULL, INT_TO_JSID(exceptions[exn].key),
                              &errProto);
    if (!ok)
        goto out;
    tv[0] = OBJECT_TO_JSVAL(errProto);

    errObject = js_NewObject(cx, &js_ErrorClass, errProto, NULL, 0);
    if (!errObject) {
        ok = JS_FALSE;
        goto out;
    }
    tv[1] = OBJECT_TO_JSVAL(errObject);

    messageStr = JS_NewStringCopyZ(cx, message);
    if (!messageStr) {
        ok = JS_FALSE;
        goto out;
    }
    tv[2] = STRING_TO_JSVAL(messageStr);

    filenameStr = JS_NewStringCopyZ(cx, reportp->filename);
    if (!filenameStr) {
        ok = JS_FALSE;
        goto out;
    }
    tv[3] = STRING_TO_JSVAL(filenameStr);

    ok = InitExnPrivate(cx, errObject, messageStr, filenameStr,
                        reportp->lineno, reportp);
    if (!ok)
        goto out;

    JS_SetPendingException(cx, OBJECT_TO_JSVAL(errObject));

    /* Flag the error report passed in to indicate an exception was raised. */
    reportp->flags |= JSREPORT_EXCEPTION;

out:
    JS_POP_TEMP_ROOT(cx, &tvr);
    cx->generatingError = JS_FALSE;
    return ok;
}
Пример #4
0
JSObject *
js_InitExceptionClasses(JSContext *cx, JSObject *obj)
{
    JSObject *obj_proto, *protos[JSEXN_LIMIT];
    int i;

    /*
     * If lazy class initialization occurs for any Error subclass, then all
     * classes are initialized, starting with Error.  To avoid reentry and
     * redundant initialization, we must not pass a null proto parameter to
     * js_NewObject below, when called for the Error superclass.  We need to
     * ensure that Object.prototype is the proto of Error.prototype.
     *
     * See the equivalent code to ensure that parent_proto is non-null when
     * JS_InitClass calls js_NewObject, in jsapi.c.
     */
    if (!js_GetClassPrototype(cx, obj, INT_TO_JSID(JSProto_Object),
                              &obj_proto)) {
        return NULL;
    }

    if (!js_EnterLocalRootScope(cx))
        return NULL;

    /* Initialize the prototypes first. */
    for (i = 0; exceptions[i].name != 0; i++) {
        JSAtom *atom;
        JSFunction *fun;
        JSString *nameString;
        int protoIndex = exceptions[i].protoIndex;

        /* Make the prototype for the current constructor name. */
        protos[i] = js_NewObject(cx, &js_ErrorClass,
                                 (protoIndex != JSEXN_NONE)
                                 ? protos[protoIndex]
                                 : obj_proto,
                                 obj, 0);
        if (!protos[i])
            break;

        /* So exn_finalize knows whether to destroy private data. */
        STOBJ_SET_SLOT(protos[i], JSSLOT_PRIVATE, JSVAL_VOID);

        /* Make a constructor function for the current name. */
        atom = cx->runtime->atomState.classAtoms[exceptions[i].key];
        fun = js_DefineFunction(cx, obj, atom, exceptions[i].native, 3, 0);
        if (!fun)
            break;

        /* Make this constructor make objects of class Exception. */
        fun->u.n.clasp = &js_ErrorClass;

        /* Make the prototype and constructor links. */
        if (!js_SetClassPrototype(cx, FUN_OBJECT(fun), protos[i],
                                  JSPROP_READONLY | JSPROP_PERMANENT)) {
            break;
        }

        /* proto bootstrap bit from JS_InitClass omitted. */
        nameString = JS_NewStringCopyZ(cx, exceptions[i].name);
        if (!nameString)
            break;

        /* Add the name property to the prototype. */
        if (!JS_DefineProperty(cx, protos[i], js_name_str,
                               STRING_TO_JSVAL(nameString),
                               NULL, NULL,
                               JSPROP_ENUMERATE)) {
            break;
        }

        /* Finally, stash the constructor for later uses. */
        if (!js_SetClassObject(cx, obj, exceptions[i].key, FUN_OBJECT(fun)))
            break;
    }

    js_LeaveLocalRootScope(cx);
    if (exceptions[i].name)
        return NULL;

    /*
     * Add an empty message property.  (To Exception.prototype only,
     * because this property will be the same for all the exception
     * protos.)
     */
    if (!JS_DefineProperty(cx, protos[0], js_message_str,
                           STRING_TO_JSVAL(cx->runtime->emptyString),
                           NULL, NULL, JSPROP_ENUMERATE)) {
        return NULL;
    }
    if (!JS_DefineProperty(cx, protos[0], js_fileName_str,
                           STRING_TO_JSVAL(cx->runtime->emptyString),
                           NULL, NULL, JSPROP_ENUMERATE)) {
        return NULL;
    }
    if (!JS_DefineProperty(cx, protos[0], js_lineNumber_str,
                           INT_TO_JSVAL(0),
                           NULL, NULL, JSPROP_ENUMERATE)) {
        return NULL;
    }

    /*
     * Add methods only to Exception.prototype, because ostensibly all
     * exception types delegate to that.
     */
    if (!JS_DefineFunctions(cx, protos[0], exception_methods))
        return NULL;

    return protos[0];
}
Пример #5
0
JSObject *
js_InitExceptionClasses(JSContext *cx, JSObject *obj)
{
    jsval roots[3];
    JSObject *obj_proto, *error_proto;
    jsval empty;

    /*
     * If lazy class initialization occurs for any Error subclass, then all
     * classes are initialized, starting with Error.  To avoid reentry and
     * redundant initialization, we must not pass a null proto parameter to
     * js_NewObject below, when called for the Error superclass.  We need to
     * ensure that Object.prototype is the proto of Error.prototype.
     *
     * See the equivalent code to ensure that parent_proto is non-null when
     * JS_InitClass calls js_NewObject, in jsapi.c.
     */
    if (!js_GetClassPrototype(cx, obj, INT_TO_JSID(JSProto_Object),
                              &obj_proto)) {
        return NULL;
    }

    memset(roots, 0, sizeof(roots));
    JSAutoTempValueRooter tvr(cx, JS_ARRAY_LENGTH(roots), roots);

#ifdef __GNUC__
    error_proto = NULL;   /* quell GCC overwarning */
#endif

    /* Initialize the prototypes first. */
    for (intN i = JSEXN_ERR; i != JSEXN_LIMIT; i++) {
        JSObject *proto;
        JSProtoKey protoKey;
        JSAtom *atom;
        JSFunction *fun;

        /* Make the prototype for the current constructor name. */
        proto = js_NewObject(cx, &js_ErrorClass,
                             (i != JSEXN_ERR) ? error_proto : obj_proto,
                             obj);
        if (!proto)
            return NULL;
        if (i == JSEXN_ERR) {
            error_proto = proto;
            roots[0] = OBJECT_TO_JSVAL(proto);
        } else {
            // We cannot share the root for error_proto and other prototypes
            // as error_proto must be rooted until the function returns.
            roots[1] = OBJECT_TO_JSVAL(proto);
        }

        /* So exn_finalize knows whether to destroy private data. */
        proto->setPrivate(NULL);

        /* Make a constructor function for the current name. */
        protoKey = GetExceptionProtoKey(i);
        atom = cx->runtime->atomState.classAtoms[protoKey];
        fun = js_DefineFunction(cx, obj, atom, Exception, 3, 0);
        if (!fun)
            return NULL;
        roots[2] = OBJECT_TO_JSVAL(FUN_OBJECT(fun));

        /* Make this constructor make objects of class Exception. */
        FUN_CLASP(fun) = &js_ErrorClass;

        /* Make the prototype and constructor links. */
        if (!js_SetClassPrototype(cx, FUN_OBJECT(fun), proto,
                                  JSPROP_READONLY | JSPROP_PERMANENT)) {
            return NULL;
        }

        /* Add the name property to the prototype. */
        if (!JS_DefineProperty(cx, proto, js_name_str, ATOM_KEY(atom),
                               NULL, NULL, JSPROP_ENUMERATE)) {
            return NULL;
        }

        /* Finally, stash the constructor for later uses. */
        if (!js_SetClassObject(cx, obj, protoKey, FUN_OBJECT(fun)))
            return NULL;
    }

    /*
     * Set default values and add methods. We do it only for Error.prototype
     * as the rest of exceptions delegate to it.
     */
    empty = STRING_TO_JSVAL(cx->runtime->emptyString);
    if (!JS_DefineProperty(cx, error_proto, js_message_str, empty,
                           NULL, NULL, JSPROP_ENUMERATE) ||
        !JS_DefineProperty(cx, error_proto, js_fileName_str, empty,
                           NULL, NULL, JSPROP_ENUMERATE) ||
        !JS_DefineProperty(cx, error_proto, js_lineNumber_str, JSVAL_ZERO,
                           NULL, NULL, JSPROP_ENUMERATE) ||
        !JS_DefineFunctions(cx, error_proto, exception_methods)) {
        return NULL;
    }

    return error_proto;
}
Пример #6
0
JSBool
js_Stringify(JSContext *cx, jsval *vp, JSObject *replacer,
             JSONWriteCallback callback, void *data, uint32 depth)
{
    if (depth > JSON_MAX_DEPTH)
        return JS_FALSE; /* encoding error */

    JSBool ok = JS_TRUE;
    JSObject *obj = JSVAL_TO_OBJECT(*vp);
    JSBool isArray = JS_IsArrayObject(cx, obj);
    jschar output = jschar(isArray ? '[' : '{');
    if (!callback(&output, 1, data))
        return JS_FALSE;

    JSObject *iterObj = NULL;
    jsint i = 0;
    jsuint length = 0;

    if (isArray) {
        if (!js_GetLengthProperty(cx, obj, &length))
            return JS_FALSE;
    } else {
        if (!js_ValueToIterator(cx, JSITER_ENUMERATE, vp))
            return JS_FALSE;
        iterObj = JSVAL_TO_OBJECT(*vp);
    }

    jsval outputValue = JSVAL_VOID;
    JSAutoTempValueRooter tvr(cx, 1, &outputValue);

    jsval key;
    JSBool memberWritten = JS_FALSE;
    do {
        outputValue = JSVAL_VOID;

        if (isArray) {
            if ((jsuint)i >= length)
                break;
            ok = OBJ_GET_PROPERTY(cx, obj, INT_TO_JSID(i), &outputValue);
            i++;
        } else {
            ok = js_CallIteratorNext(cx, iterObj, &key);
            if (!ok)
                break;
            if (key == JSVAL_HOLE)
                break;

            JSString *ks;
            if (JSVAL_IS_STRING(key)) {
                ks = JSVAL_TO_STRING(key);
            } else {
                ks = js_ValueToString(cx, key);
                if (!ks) {
                    ok = JS_FALSE;
                    break;
                }
            }

            ok = JS_GetUCProperty(cx, obj, JS_GetStringChars(ks),
                                  JS_GetStringLength(ks), &outputValue);
        }

        if (!ok)
            break;

        // if this is an array, holes are transmitted as null
        if (isArray && outputValue == JSVAL_VOID) {
            outputValue = JSVAL_NULL;
        } else if (JSVAL_IS_OBJECT(outputValue)) {
            ok = js_TryJSON(cx, &outputValue);
            if (!ok)
                break;
        }

        // elide undefined values
        if (outputValue == JSVAL_VOID)
            continue;

        // output a comma unless this is the first member to write
        if (memberWritten) {
            output = jschar(',');
            ok = callback(&output, 1, data);
        if (!ok)
                break;
        }
        memberWritten = JS_TRUE;

        JSType type = JS_TypeOfValue(cx, outputValue);

        // Can't encode these types, so drop them
        if (type == JSTYPE_FUNCTION || type == JSTYPE_XML)
            break;

        // Be careful below, this string is weakly rooted.
        JSString *s;

        // If this isn't an array, we need to output a key
        if (!isArray) {
            s = js_ValueToString(cx, key);
            if (!s) {
                ok = JS_FALSE;
                break;
            }

            ok = write_string(cx, callback, data, JS_GetStringChars(s), JS_GetStringLength(s));
            if (!ok)
                break;

            output = jschar(':');
            ok = callback(&output, 1, data);
            if (!ok)
                break;
        }

        if (!JSVAL_IS_PRIMITIVE(outputValue)) {
            // recurse
            ok = js_Stringify(cx, &outputValue, replacer, callback, data, depth + 1);
        } else {
            JSString *outputString;
            s = js_ValueToString(cx, outputValue);
            if (!s) {
                ok = JS_FALSE;
                break;
            }

            if (type == JSTYPE_STRING) {
                ok = write_string(cx, callback, data, JS_GetStringChars(s), JS_GetStringLength(s));
                if (!ok)
                    break;

                continue;
            }

            if (type == JSTYPE_NUMBER) {
                if (JSVAL_IS_DOUBLE(outputValue)) {
                    jsdouble d = *JSVAL_TO_DOUBLE(outputValue);
                    if (!JSDOUBLE_IS_FINITE(d))
                        outputString = JS_NewStringCopyN(cx, "null", 4);
                    else
                        outputString = s;
                } else {
                    outputString = s;
                }
            } else if (type == JSTYPE_BOOLEAN) {
                outputString = s;
            } else if (JSVAL_IS_NULL(outputValue)) {
                outputString = JS_NewStringCopyN(cx, "null", 4);
            } else {
                ok = JS_FALSE; // encoding error
                break;
            }

            if (!outputString) {
                ok = JS_FALSE;
                break;
            }

            ok = callback(JS_GetStringChars(outputString), JS_GetStringLength(outputString), data);
        }
    } while (ok);

    if (iterObj) {
        // Always close the iterator, but make sure not to stomp on OK
        ok &= js_CloseIterator(cx, *vp);
        // encoding error or propagate? FIXME: Bug 408838.
    }

    if (!ok) {
        JS_ReportError(cx, "Error during JSON encoding");
        return JS_FALSE;
    }

    output = jschar(isArray ? ']' : '}');
    ok = callback(&output, 1, data);

    return ok;
}