/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is The JavaScript Debugger.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Robert Ginda, <rginda@netscape.com>, original author
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

function initRecords()
{
    var cmdary =
        [/* "real" commands */
         ["show-functions",  cmdShowFunctions,                     CMD_CONSOLE],
         ["show-ecmas",      cmdShowECMAs,                         CMD_CONSOLE],
         ["show-constants",  cmdShowConstants,                     CMD_CONSOLE],

         /* aliases */
         ["toggle-functions",     "show-functions toggle",                   0],
         ["toggle-ecmas",         "show-ecma toggle",                        0],
         ["toggle-constants",     "show-constants toggle",                   0]
        ];

    console.commandManager.defineCommands (cmdary);

    var atomsvc = console.atomService;

    WindowRecord.prototype.property         = atomsvc.getAtom("item-window");

    FileContainerRecord.prototype.property  = atomsvc.getAtom("item-files");

    FileRecord.prototype.property           = atomsvc.getAtom("item-file");

    FrameRecord.prototype.property    = atomsvc.getAtom("item-frame");
    FrameRecord.prototype.atomCurrent = atomsvc.getAtom("current-frame-flag");

    ScriptInstanceRecord.prototype.atomUnknown = atomsvc.getAtom("item-unk");
    ScriptInstanceRecord.prototype.atomHTML    = atomsvc.getAtom("item-html");
    ScriptInstanceRecord.prototype.atomJS      = atomsvc.getAtom("item-js");
    ScriptInstanceRecord.prototype.atomXUL     = atomsvc.getAtom("item-xul");
    ScriptInstanceRecord.prototype.atomXML     = atomsvc.getAtom("item-xml");
    ScriptInstanceRecord.prototype.atomDisabled = 
        atomsvc.getAtom("script-disabled");

    ScriptRecord.prototype.atomFunction   = atomsvc.getAtom("file-function");
    ScriptRecord.prototype.atomBreakpoint = atomsvc.getAtom("item-has-bp");
    ScriptRecord.prototype.atomDisabled   = atomsvc.getAtom("script-disabled");

    var prefs =
        [
         ["valueRecord.showFunctions", false],
         ["valueRecord.showECMAProps", false],
         ["valueRecord.showConstants", false],
         ["valueRecord.brokenObjects", "^JavaPackage$"]
        ];

    console.prefManager.addPrefs(prefs);
    
    try
    {
        ValueRecord.prototype.brokenObjects = 
            new RegExp (console.prefs["valueRecord.brokenObjects"]);
    }
    catch (ex)
    {
        display (MSN_ERR_INVALID_PREF,
                 ["valueRecord.brokenObjects",
                  console.prefs["valueRecord.brokenObjects"]], MT_ERROR);
        display (formatException(ex), MT_ERROR);
        ValueRecord.prototype.brokenObjects = /^JavaPackage$/;
    }
                  
    ValueRecord.prototype.atomVoid      = atomsvc.getAtom("item-void");
    ValueRecord.prototype.atomNull      = atomsvc.getAtom("item-null");
    ValueRecord.prototype.atomBool      = atomsvc.getAtom("item-bool");
    ValueRecord.prototype.atomInt       = atomsvc.getAtom("item-int");
    ValueRecord.prototype.atomDouble    = atomsvc.getAtom("item-double");
    ValueRecord.prototype.atomString    = atomsvc.getAtom("item-string");
    ValueRecord.prototype.atomFunction  = atomsvc.getAtom("item-function");
    ValueRecord.prototype.atomObject    = atomsvc.getAtom("item-object");
    ValueRecord.prototype.atomError     = atomsvc.getAtom("item-error");
    ValueRecord.prototype.atomException = atomsvc.getAtom("item-exception");
    ValueRecord.prototype.atomHinted    = atomsvc.getAtom("item-hinted");
}

/*******************************************************************************
 * Breakpoint Record.
 * One prototype for all breakpoint types, works only in the Breakpoints View.
 *******************************************************************************/

function BPRecord (breakWrapper)
{
    this.setColumnPropertyName ("col-0", "name");
    this.setColumnPropertyName ("col-1", "line");

    this.breakWrapper = breakWrapper;
    
    if ("pc" in breakWrapper)
    {
        this.type = "instance";
        this.name = breakWrapper.scriptWrapper.functionName;
        this.line = getMsg(MSN_FMT_PC, String(breakWrapper.pc));
    }
    else if (breakWrapper instanceof FutureBreakpoint)
    {
        this.type = "future";
        var ary = breakWrapper.url.match(/\/([^\/?]+)(\?|$)/);
        if (ary)
            this.name = ary[1];
        else
            this.name = breakWrapper.url;

        this.line = breakWrapper.lineNumber;
    }    
}

BPRecord.prototype = new XULTreeViewRecord(console.views.breaks.share);

/*******************************************************************************
 * Stack Frame Record.
 * Represents a jsdIStackFrame, for use in the Stack View only.
 *******************************************************************************/

function FrameRecord (jsdFrame)
{
    if (!(jsdFrame instanceof jsdIStackFrame))
        throw new BadMojo (ERR_INVALID_PARAM, "value");

    this.setColumnPropertyName ("col-0", "functionName");
    this.setColumnPropertyName ("col-1", "location");

    this.functionName = jsdFrame.functionName;
    if (!jsdFrame.isNative)
    {
        this.scriptWrapper = getScriptWrapper(jsdFrame.script);
        this.location = getMsg(MSN_FMT_FRAME_LOCATION,
                               [getFileFromPath(jsdFrame.script.fileName),
                                jsdFrame.line, jsdFrame.pc]);
        this.functionName = this.scriptWrapper.functionName;
    }
    else
    {
        this.scriptWrapper = null;
        this.location = MSG_URL_NATIVE;
    }
    
    this.jsdFrame = jsdFrame;
}

FrameRecord.prototype = new XULTreeViewRecord (console.views.stack.share);

/*******************************************************************************
 * Window Record.
 * Represents a DOM window with files and child windows.  For use in the
 * Windows View.
 *******************************************************************************/

function WindowRecord (win, baseURL)
{
    function none() { return ""; };
    this.setColumnPropertyName ("col-0", "displayName");

    this.window = win;
    this.url = win.location.href;
    if (this.url.search(/^\w+:/) == -1)
    {
        if (this.url[0] == "/")
        {
            this.url = win.location.protocol + "//" + win.location.host + 
                this.url;
        }
        else
        {
            this.url = baseURL + this.url;
        }
        this.baseURL = baseURL;
    }
    else
    {
        this.baseURL = getPathFromURL(this.url);
        if (this.baseURL.indexOf("file:///") == 0)
            this.baseURL = "file:/" + this.baseURL.substr(8)
    }
    
    this.reserveChildren(true);
    this.shortName = getFileFromPath (this.url);
    if (console.prefs["enableChromeFilter"] && this.shortName == "navigator.xul")
    {
        this.displayName = MSG_NAVIGATOR_XUL;
    }
    else if (console.prefs["enableChromeFilter"] && this.shortName == "browser.xul")
    {
        this.displayName = MSG_BROWSER_XUL;
    }
    else
    {
        this.filesRecord = new FileContainerRecord(this);
        this.displayName = this.shortName;
    }
    
}

WindowRecord.prototype = new XULTreeViewRecord(console.views.windows.share);

WindowRecord.prototype.onPreOpen =
function wr_preopen()
{   
    this.childData = new Array();

    if ("filesRecord" in this)
        this.appendChild(this.filesRecord);    

    var framesLength = this.window.frames.length;
    for (var i = 0; i < framesLength; ++i)
    {
        this.appendChild(new WindowRecord(this.window.frames[i].window,
                                          this.baseURL));
    }
}
    
/*******************************************************************************
 * "File Container" Record.
 * List of script tags found in the parent record's |window.document| property.
 * For use in the Windows View, as a child of a WindowRecord.
 *******************************************************************************/

function FileContainerRecord (windowRecord)
{
    function files() { return MSG_FILES_REC; }
    function none() { return ""; }
    this.setColumnPropertyName ("col-0", files);
    this.windowRecord = windowRecord;
    this.reserveChildren(true);
}

FileContainerRecord.prototype =
    new XULTreeViewRecord(console.views.windows.share);

FileContainerRecord.prototype.onPreOpen =
function fcr_getkids ()
{
    if (!("parentRecord" in this))
        return;
    
    this.childData = new Array();
    var doc = this.windowRecord.window.document;
    var loc = this.windowRecord.window.location;
    var nodeList = doc.getElementsByTagName("script");

    for (var i = 0; i < nodeList.length; ++i)
    {
        var url = nodeList.item(i).getAttribute("src");
        if (url)
        {
            if (url.search(/^\w+:/) == -1)
            {
                if (url[0] == "/")
                    url = loc.protocol + "//" + loc.host + url;
                else
                    url = this.windowRecord.baseURL + url;
            }
            else
            {
                this.baseURL = getPathFromURL(url);
            }

            this.appendChild(new FileRecord(url));
        }
    }
}

/*******************************************************************************
 * File Record
 * Represents a URL, for use in the Windows View only.
 *******************************************************************************/

function FileRecord (url)
{
    function none() { return ""; }
    this.setColumnPropertyName ("col-0", "shortName");
    this.url = url;
    this.shortName = getFileFromPath(url);
}

FileRecord.prototype = new XULTreeViewRecord(console.views.windows.share);

/*******************************************************************************
 * Script Instance Record.
 * Represents a ScriptInstance, for use in the Scripts View only.
 *******************************************************************************/

function ScriptInstanceRecord(scriptInstance)
{
    if (!ASSERT(scriptInstance.isSealed,
                "Attempt to create ScriptInstanceRecord for unsealed instance"))
    {
        return null;
    }
    
    this.setColumnPropertyName ("col-0", "displayName");
    this.setColumnPropertyValue ("col-1", "");
    this.setColumnPropertyValue ("col-2", "");
    this.reserveChildren(true);
    this.url = scriptInstance.url;
    var sv = console.views.scripts;
    this.fileType = this.atomUnknown;
    this.shortName = this.url;
    this.group = 4;
    this.scriptInstance = scriptInstance;
    this.lastScriptCount = 0;
    this.sequence = scriptInstance.sequence;
    
    this.shortName = getFileFromPath(this.url);
    var ary = this.shortName.match (/\.(js|html|xul|xml)$/i);
    if (ary)
    {
        switch (ary[1].toLowerCase())
        {
            case "js":
                this.fileType = this.atomJS;
                this.group = 0;
                break;
            
            case "html":
                this.group = 1;
                this.fileType = this.atomHTML;
                break;
            
            case "xul":
                this.group = 2;
                this.fileType = this.atomXUL;
                break;
            
            case "xml":
                this.group = 3;
                this.fileType = this.atomXML;
                break;
        }
    }
    
    this.displayName = this.shortName;
    this.sortName = this.shortName.toLowerCase();
    return this;
}

ScriptInstanceRecord.prototype =
    new XULTreeViewRecord(console.views.scripts.share);

ScriptInstanceRecord.prototype.onDragStart =
function scr_dragstart (e, transferData, dragAction)
{        
    transferData.data = new TransferData();
    transferData.data.addDataForFlavour("text/x-venkman-file", this.fileName);
    transferData.data.addDataForFlavour("text/x-moz-url", this.fileName);
    transferData.data.addDataForFlavour("text/unicode", this.fileName);
    transferData.data.addDataForFlavour("text/html",
                                        "<a href='" + this.fileName +
                                        "'>" + this.fileName + "</a>");
    return true;
}    

ScriptInstanceRecord.prototype.getProperties =
function scr_getprops (properties)
{
    properties.AppendElement(this.fileType);
    
    if (this.scriptInstance.disabledScripts > 0)
        properties.AppendElement (this.atomDisabled);
}

ScriptInstanceRecord.prototype.super_resort = XTRootRecord.prototype.resort;

ScriptInstanceRecord.prototype.resort =
function scr_resort ()
{
    console._groupFiles = console.prefs["scriptsView.groupFiles"];
    this.super_resort();
    delete console._groupFiles;
}

ScriptInstanceRecord.prototype.sortCompare =
function scr_compare (a, b)
{
    if (0)
    {
        if (a.group < b.group)
            return -1;
    
        if (a.group > b.group)
            return 1;
    }
    
    if (a.sortName < b.sortName)
        return -1;

    if (a.sortName > b.sortName)
        return 1;

    if (a.sequence < b.sequence)
        return -1;

    if (a.sequence > b.sequence)
        return 1;
    
    dd ("ack, all equal?");
    return 0;
}

ScriptInstanceRecord.prototype.onPreOpen =
function scr_preopen ()
{
    if (!this.scriptInstance.sourceText.isLoaded)
        this.scriptInstance.sourceText.loadSource();
    
    if (this.lastScriptCount != this.scriptInstance.scriptCount)
    {
        var sr;

        console.views.scripts.freeze();
        this.childData = new Array();
        var scriptWrapper = this.scriptInstance.topLevel;
        if (scriptWrapper)
        {
            sr = new ScriptRecord(scriptWrapper);
            scriptWrapper.scriptRecord = sr;
            this.appendChild(sr);
        }

        var functions = this.scriptInstance.functions;
        for (var f in functions)
        {
            if (functions[f].jsdScript.isValid)
            {
                sr = new ScriptRecord(functions[f]);
                functions[f].scriptRecord = sr;
                this.appendChild(sr);
            }
        }
        console.views.scripts.thaw();
        this.lastScriptCount = this.scriptInstance.scriptCount;
    }
}

/*******************************************************************************
 * Script Record.
 * Represents a ScriptWrapper, for use in the Scripts View only.
 *******************************************************************************/

function ScriptRecord(scriptWrapper) 
{
    this.setColumnPropertyName ("col-0", "functionName");
    this.setColumnPropertyName ("col-1", "baseLineNumber");
    this.setColumnPropertyName ("col-2", "lineExtent");

    this.functionName = scriptWrapper.functionName
    this.baseLineNumber = scriptWrapper.jsdScript.baseLineNumber;
    this.lineExtent = scriptWrapper.jsdScript.lineExtent;
    this.scriptWrapper = scriptWrapper;

    this.jsdurl = "jsd:sourcetext?url=" +
        encodeURIComponent(this.scriptWrapper.jsdScript.fileName) + 
        "&base=" + this.baseLineNumber + "&extent=" + this.lineExtent +
        "&name=" + this.functionName;
}

ScriptRecord.prototype = new XULTreeViewRecord(console.views.scripts.share);

ScriptRecord.prototype.onDragStart =
function sr_dragstart (e, transferData, dragAction)
{        
    var fileName = this.script.fileName;
    transferData.data = new TransferData();
    transferData.data.addDataForFlavour("text/x-jsd-url", this.jsdurl);
    transferData.data.addDataForFlavour("text/x-moz-url", fileName);
    transferData.data.addDataForFlavour("text/unicode", fileName);
    transferData.data.addDataForFlavour("text/html",
                                        "<a href='" + fileName +
                                        "'>" + fileName + "</a>");
    return true;
}

ScriptRecord.prototype.getProperties =
function sr_getprops (properties)
{
    properties.AppendElement (this.atomFunction);

    if (this.scriptWrapper.breakpointCount)
        properties.AppendElement (this.atomBreakpoint);

    if (this.scriptWrapper.jsdScript.isValid &&
        this.scriptWrapper.jsdScript.flags & SCRIPT_NODEBUG)
    {
        properties.AppendElement (this.atomDisabled);
    }
}

/*******************************************************************************
 * Value Record.
 * Use this to show a jsdIValue in any tree.
 *******************************************************************************/

function cmdShowFunctions (e)
{
    if (e.toggle != null)
    {
        e.toggle = getToggle(e.toggle, ValueRecord.prototype.showFunctions);
        ValueRecord.prototype.showFunctions = e.toggle;
        console.prefs["valueRecord.showFunctions"] = e.toggle;
    }

    if ("isInteractive" in e && e.isInteractive)
        dispatch("pref valueRecord.showFunctions", { isInteractive: true });
}

function cmdShowECMAs (e)
{
    if (e.toggle != null)
    {
        e.toggle = getToggle(e.toggle, ValueRecord.prototype.showECMAProps);
        ValueRecord.prototype.showECMAProps = e.toggle;
        console.prefs["valueRecord.showECMAProps"] = e.toggle;
    }

    if ("isInteractive" in e && e.isInteractive)
        dispatch("pref valueRecord.showECMAProps", { isInteractive: true });
}

function cmdShowConstants (e)
{
    if (e.toggle != null)
    {
        e.toggle = getToggle(e.toggle, ValueRecord.prototype.showConstants);
        ValueRecord.prototype.showConstants = e.toggle;
        console.prefs["valueRecord.showConstants"] = e.toggle;
    }

    if ("isInteractive" in e && e.isInteractive)
        dispatch("pref valueRecord.showConstants", { isInteractive: true });
}

function ValueRecord (value, name, flags, jsdFrame)
{
    if (!(value instanceof jsdIValue))
        throw new BadMojo (ERR_INVALID_PARAM, "value", String(value));

    this.setColumnPropertyName ("col-0", "displayName");
    this.setColumnPropertyName ("col-1", "displayType");
    this.setColumnPropertyName ("col-2", "displayValue");
    this.setColumnPropertyName ("col-3", "displayFlags");    
    this.displayName = name;
    this.displayFlags = formatFlags(flags);
    this.name = name;
    this.flags = flags;
    this.value = value;
    this.jsdFrame = jsdFrame;
    this.jsType = null;
    this.onPreRefresh = false;
    this.refresh();
    delete this.onPreRefresh;
}

ValueRecord.prototype = new XULTreeViewRecord (null);

ValueRecord.prototype.__defineGetter__("_share", vr_getshare);
function vr_getshare()
{
    if ("__share" in this)
        return this.__share;
     
    if ("parentRecord" in this)
        return this.__share = this.parentRecord._share;
 
    ASSERT (0, "ValueRecord cannot be the root of a visible tree.");
    return null;
}

ValueRecord.prototype.__defineGetter__("expression", vr_getexpressionl);
function vr_getexpressionl()
{
    return this.getExpression();
}

ValueRecord.prototype.getExpression =
function vr_getexpression(extra)
{
    var items = [this.displayName];

    if ("value" in this.parentRecord)
    {
        var cur = this.parentRecord;
        while (!("isRootRecord" in cur) || !cur.isRootRecord)
        {
            if ("isECMAProto" in cur)
                items.unshift("__proto__");
            else if ("isECMAParent" in cur)
                items.unshift("__parent__");
            else
                items.unshift(cur.displayName);
            cur = cur.parentRecord;
        }
    }

    if (typeof extra == "string")
        items.push(extra);

    return makeExpression(items);
}

ValueRecord.prototype.evalString =
function vr_evalstring(string)
{
    //dd("ValueRecord(" + this.displayName + ").evalString(" + string + ")");
    var rval = new Object();
    if (this.jsdFrame.eval(string, JSD_URL_SCHEME + "value-record", 1, rval))
        return rval.value;
    return undefined;
}

ValueRecord.prototype.showFunctions = false;
ValueRecord.prototype.showECMAProps = false;
ValueRecord.prototype.showConstants = false;

ValueRecord.prototype.getProperties =
function vr_getprops (properties)
{
    if ("valueIsException" in this || this.flags & PROP_EXCEPTION)
        properties.AppendElement (this.atomException);

    if (this.flags & PROP_ERROR)
        properties.AppendElement (this.atomError);

    if (this.flags & PROP_HINTED)
        properties.AppendElement (this.atomHinted);

    properties.AppendElement (this.property);
}

ValueRecord.prototype.onPreRefresh =
function vr_prerefresh ()
{
    if (!ASSERT("parentRecord" in this, "onPreRefresh with no parent"))
        return;
    
    if ("isECMAProto" in this)
    {
        if (this.parentRecord.value.jsPrototype)
            this.value = this.parentRecord.value.jsPrototype;
        else
            this.value = console.jsds.wrapValue(null);
    }
    else if ("isECMAParent" in this)
    {
        if (this.parentRecord.value.jsParent)
            this.value = this.parentRecord.value.jsParent;
        else
            this.value = console.jsds.wrapValue(null);
    }
    else
    {
        var value = this.parentRecord.value;
        var prop = value.getProperty (this.name);
        if (prop)
        {
            this.flags = prop.flags;
            this.value = prop.value;
        }
        else
        {
            ASSERT(this.jsdFrame, "ValueRecord(" + this.displayName +
                   ").onPreRefresh: no jsdIStackFrame to safely eval on!");

            this.value = this.evalString(this.expression);
            this.flags = PROP_ENUMERATE | PROP_HINTED;
        }
    }
}
    
ValueRecord.prototype.refresh =
function vr_refresh ()
{
    if (this.onPreRefresh)
    {
        try
        {
            this.onPreRefresh();
            delete this.valueIsException;
        }
        catch (ex)
        {
            /*
            dd ("caught exception refreshing " + this.displayName);
            if (typeof ex == "object")
                dd (dumpObjectTree(ex));
            else
                dd(ex);
            */
            if (!(ex instanceof jsdIValue))
                ex = console.jsds.wrapValue(ex);
            
            this.value = ex;
            this.valueIsException = true;
        }
    }

    this.jsType = this.value.jsType;
    delete this.alwaysHasChildren;

    var strval;
    
    switch (this.jsType)
    {
        case TYPE_VOID:
            this.displayValue = MSG_TYPE_VOID
            this.displayType  = MSG_TYPE_VOID;
            this.property     = this.atomVoid;
            break;
        case TYPE_NULL:
            this.displayValue = MSG_TYPE_NULL;
            this.displayType  = MSG_TYPE_NULL;
            this.property     = this.atomNull;
            break;
        case TYPE_BOOLEAN:
            this.displayValue = this.value.stringValue;
            this.displayType  = MSG_TYPE_BOOLEAN;
            this.property     = this.atomBool;
            break;
        case TYPE_INT:
            this.displayValue = this.value.intValue;
            this.displayType  = MSG_TYPE_INT;
            this.property     = this.atomInt;
            break;
        case TYPE_DOUBLE:
            this.displayValue = this.value.doubleValue;
            this.displayType  = MSG_TYPE_DOUBLE;
            this.property     = this.atomDouble;
            break;
        case TYPE_STRING:
            strval = this.value.stringValue;
            if (strval.length > console.prefs["maxStringLength"])
                strval = getMsg(MSN_FMT_LONGSTR, strval.length);
            else
                strval = strval.quote();
            this.displayValue = strval;
            this.displayType  = MSG_TYPE_STRING;
            this.property     = this.atomString;
            break;
        case TYPE_FUNCTION:
        case TYPE_OBJECT:
            this.displayType = MSG_TYPE_OBJECT;
            this.property = this.atomObject;

            this.alwaysHasChildren = true;
            this.value.refresh();

            var ctor = this.value.jsClassName;
            strval = null;
            
            switch (ctor)
            {
                case "Function":
                    this.displayType  = MSG_TYPE_FUNCTION;
                    ctor = (this.value.isNative ? MSG_CLASS_NATIVE_FUN :
                            MSG_CLASS_SCRIPT_FUN);
                    this.property = this.atomFunction;
                    break;

                case "Object":
                    if (this.value.jsConstructor)
                        ctor = this.value.jsConstructor.jsFunctionName;
                    break;

                case "XPCWrappedNative_NoHelper":
                    ctor = MSG_CLASS_CONST_XPCOBJ;
                    break;

                case "XPC_WN_ModsAllowed_Proto_JSClass":
                    ctor = MSG_CLASS_XPCOBJ;
                    break;

                case "String":
                    strval = this.value.stringValue;
                    if (strval.length > console.prefs["maxStringLength"])
                        strval = getMsg(MSN_FMT_LONGSTR, strval.length);
                    else
                        strval = strval.quote();
                    break;

                case "Number":
                case "Boolean":
                    strval = this.value.stringValue;
                    break;
            }

            if (strval != null)
            {
                this.displayValue = strval;
            }
            else
            {
                if (0) {
                    /* too slow... */
                    var propCount;
                    if (this.brokenObjects.test(ctor))
                    {
                        /* XXX these objects do Bad Things when you enumerate
                         * over them. */
                        propCount = 0;
                    }
                    else
                    {
                        propCount = this.countProperties();
                    }
                
                    this.displayValue = getMsg (MSN_FMT_OBJECT_VALUE,
                                                [ctor, propCount]);
                }
                else
                {
                    this.displayValue = "{" + ctor + "}";
                }
            }
            
            /* if we have children, refresh them. */
            if ("childData" in this && this.childData.length > 0)
            {
                if (!this.refreshChildren())
                {
                    dd ("refreshChilren failed for " + this.displayName);
                    delete this.childData;
                    this.close();
                }
            }
            break;

        default:
            ASSERT (0, "invalid value");
    }
}

ValueRecord.prototype.countProperties =
function vr_countprops ()
{
    ASSERT(this.jsdFrame, "ValueRecord(" + this.displayName +
           ").countProperties: no jsdIStackFrame to safely eval on!");

    // Note: uses an inline function to avoid polluting the frame's scope.
    var code = "(function(obj){" +
               "    var count = 0;" +
               "    for (var prop in obj)" +
               "        ++count;" +
               "    return count;" +
               "})(" + this.expression + ")";

    // rv is undefined if an exception occured.
    var rv = this.evalString(code);
    if (typeof rv == "undefined")
        return 0;

    return rv.intValue;
}

ValueRecord.prototype.listProperties =
function vr_listprops ()
{
    function charEscapeReplace(s, c)
    {
        return String.fromCharCode(parseInt(c, 16));
    };

    // the ":" prefix for keys in the propMap avoid collisions with "real"
    // pseudo-properties, such as __proto__.  If we were to actually assign
    // to those we would introduce bad side affects.

    //dd ("listProperties {");
    var i, jsval;
    var propMap = new Object();

    /* get the enumerable properties */

    ASSERT(this.jsdFrame, "ValueRecord(" + this.displayName +
           ").listProperties: no jsdIStackFrame to safely eval on!");

    var propList = new Array();

    // quote() puts double-quotes at either end of the string,
    // backspash-escapes double-quotes in the string, and (quite
    // importantly) uses \xXX and \uXXXX escapes for non-ASCII
    // characters.

    // Note: uses an inline function to avoid polluting the frame's scope.
    var code = "(function(obj){" +
               "    var string = '';" +
               "    for (var prop in obj) {" +
               "        if (string)" +
               "            string += ',';" +
               "        string += prop.quote();" +
               "    }" +
               "    return string;" +
               "})(" + this.expression + ")";

    // list is undefined if an exception occured.
    var list = this.evalString(code);
    if (typeof list != "undefined") {
        list = list.stringValue;
        //dd("ValueRecord(" + this.displayName +
        //   ").listProperties: list: " + list);
        if (list) {
            list = ('",' + list + ',"').split('","');

            for (i = 0; i < list.length; i++)
            {
                if (!list[i])
                    continue;

                var prop = list[i];
                prop = prop.replace(/\\x([0-9a-f]{2})/i, charEscapeReplace);
                prop = prop.replace(/\\u([0-9a-f]{4})/i, charEscapeReplace);
                prop = prop.replace(/\\(.)/, "$1");
                propList.push(prop);
                //dd("ValueRecord(" + this.displayName +
                //   ").listProperties: prop: " + prop);
            }
        }
    }

    for (i = 0; i < propList.length; i++)
    {
        var p = propList[i];
        var value;
        try
        {
            value = this.evalString(this.getExpression(p));

            if (this.showFunctions || value.jsType != TYPE_FUNCTION)
            {
                propMap[":" + p] = { name: p, value: value,
                                     flags: PROP_ENUMERATE | PROP_HINTED };
            }
            else
            {
                //dd ("not including function " + name);
                propMap[":" + p] = null;
            }
        }
        catch (ex)
        {
            propMap[":" + p] = { name: p, value: console.jsds.wrapValue(ex),
                                 flags: PROP_EXCEPTION };
        }

        //dd ("jsval props: adding " + p + ", " + propMap[":" + p]);

    }
    
    /* get the local properties, may or may not be enumerable */
    var localProps = new Object()
    this.value.getProperties(localProps, {});
    localProps = localProps.value;
    var len = localProps.length;
    for (i = 0; i < len; ++i)
    {
        var prop = localProps[i];
        var name = prop.name.stringValue;
        
        if (!((":" + name) in propMap))
        {
            if ((this.showFunctions || prop.value.jsType != TYPE_FUNCTION) && 
                (this.showConstants || !(prop.flags & PROP_READONLY)))
            {
                //dd ("localProps: adding " + name + ", " + prop);
                propMap[":" + name] = { name: name, value: prop.value,
                                        flags: prop.flags };
            }
            else
            {
                //dd ("not including function " + name);
                propMap[":" + name] = null;
            }
        }
        else
        {
            if (!this.showConstants && (prop.flags & PROP_READONLY))
                propMap[":" + name] = null;
            if (propMap[":" + name])
                propMap[":" + name].flags = prop.flags;
        }
    }
    
    /* sort the property list */
    var nameList = keys(propMap);
    //dd ("nameList is " + nameList);
    
    nameList.sort();
    var propertyList = new Array();
    for (i = 0; i < nameList.length; ++i)
    {
        name = nameList[i];
        if (propMap[name])
            propertyList.push (propMap[name]);
    }
    
    //dd ("} " + propertyList.length + " properties");

    return propertyList;
}

ValueRecord.prototype.refreshChildren =
function vr_refreshkids ()
{
    var leadingProps = 0;
    
    this.propertyList = this.listProperties();

    for (var i = 0; i < this.childData.length; ++i)
    {
        if ("isECMAParent" in this.childData[i] ||
            "isECMAProto" in this.childData[i])
        {
            ++leadingProps;
        }
        else if (this.childData.length - leadingProps != 
                 this.propertyList.length)
        {
            dd ("refreshChildren: property length mismatch");
            return false;
        }
        else if (this.childData[i]._colValues["col-0"] !=
                 this.propertyList[i - leadingProps].name)
        {
            dd ("refreshChildren: " +
                this.childData[i]._colValues["col-0"] + " != " +
                this.propertyList[i - leadingProps].name);
            return false;
        }

        this.childData[i].refresh();
    }

    if (this.childData.length - leadingProps != 
        this.propertyList.length)
    {
        dd ("refreshChildren: property length mismatch");
        return false;
    }
    return true;
}

ValueRecord.prototype.onPreOpen =
function vr_preopen()
{
    try
    {
        if (!ASSERT(this.value.jsType == TYPE_OBJECT || 
                    this.value.jsType == TYPE_FUNCTION,
                    "onPreOpen called for non object?"))
        {
            return;
        }
        
        this.childData = new Array();
        this.propertyList = this.listProperties();
        
        if (this.showECMAProps)
        {
            var rec;
            if (this.value.jsPrototype)
            {
                rec = new ValueRecord(this.value.jsPrototype,
                                      MSG_VAL_PROTO, "", this.jsdFrame);
                rec.isECMAProto = true;
                this.appendChild (rec);
            }
            
            if (this.value.jsParent)
            {
                rec = new ValueRecord(this.value.jsParent,
                                      MSG_VAL_PARENT, "", this.jsdFrame);
                rec.isECMAParent = true;
                this.appendChild (rec);
            }
        }
        
        if (!this.childData.length && !this.propertyList.length)
        {
            rec = new XTLabelRecord ("col-0", MSG_VAL_NONE,
                                     ["col-1", "col-2", "col-3"]);
            this.appendChild(rec);
            return;
        }
        
        for (var i = 0; i < this.propertyList.length; ++i)
        {
            var prop = this.propertyList[i];
            this.appendChild(new ValueRecord(prop.value,
                                             prop.name,
                                             prop.flags,
                                             this.jsdFrame));
        }
    }
    catch (ex)
    {
        display (getMsg (MSN_ERR_FAILURE, ex), MT_ERROR);
    }
}

ValueRecord.prototype.onPostClose =
function vr_destroy()
{
    this.childData = new Array();
    delete this.propertyList;
}
