GM_config

GM_config library

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/2855/33973/GM_config.js

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        GM_config
// @namespace   GM_config
// @description GM_config library
// @source      https://github.com/Zod-/GM_config
// @version     1.1.2

// @copyright   sizzlemctwizzle
// @copyright   Zod-

// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_deleteValue
// @grant       GM_log
// @license     LGPL 3
// ==/UserScript==
/*
Copyright 2009-2013, GM_config Contributors
All rights reserved.

GM_config Contributors:
    Mike Medley <[email protected]>
    Joe Simmons
    Izzy Soft
    Marti Martz

GM_config is distributed under the terms of the GNU Lesser General Public License.

    GM_config is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

// The GM_config constructor
function GM_configStruct() {
    // call init() if settings were passed to constructor
    if (arguments.length) {
        GM_configInit(this, arguments);
        this.onInit();
    }
}

function removeSectionDuplicates(fields) {
    var section,
        subsection,
        field;
    for (var i = 0; i < fields.length; i += 1) {
        field = fields[i];
        if (typeof field.section !== 'undefined') {
            if (field.section[0] === section) {
                if (field.section.length > 1) {
                    if (field.section[1] === subsection) {
                        field.section = undefined;
                    } else {
                        subsection = field.section[1];
                        field.section = [undefined, subsection];
                    }
                } else {
                    field.section = undefined;
                }
            } else {
                section = field.section[0];
                subsection = (field.section.length > 1) ? field.section[1] : undefined;
            }
        }
    }
    return fields;
}

function compareField(a, b) {
        var r = 0;
        if (typeof a.section === 'undefined' || typeof b.section === 'undefined') {
            if(typeof a.section === 'undefined' && typeof b.section === 'undefined'){
                r = a.label.localeCompare(b.label);
            }else if(typeof a.section === 'undefined'){
                r = -1;
            }else{
                r = 1;
            }
        } else if (a.section[0] === b.section[0]) {
            if (a.section.length !== b.section.length) {
                r = a.section.length - b.section.length;
            } else if (a.section.length === 1 && b.section.length === 1) {
                r = a.label.localeCompare(b.label);
            } else {
                if (a.section[1] === b.section[1]) {
                    r = a.label.localeCompare(b.label);
                } else {
                    r = a.section[1].localeCompare(b.section[1]);
                }
            }
        } else {
            r = a.section[0].localeCompare(b.section[0]);
        }
        return r;
    }
    // This is the initializer function
function GM_configInit(config, args) {
    // Initialize instance variables
    if (typeof config.fields == "undefined") {
        config.fields = {};
        config.onInit = config.onInit || function () {};
        config.onOpen = config.onOpen || function () {};
        config.onSave = config.onSave || function () {};
        config.onClose = config.onClose || function () {};
        config.onReset = config.onReset || function () {};
        config.isOpen = false;
        config.title = 'User Script Settings';
        config.css = {
            basic: [
                "#GM_config * { font-family: arial,tahoma,myriad pro,sans-serif; }",
                "#GM_config { background: #FFF; }",
                "#GM_config input[type='radio'] { margin-right: 8px; }",
                "#GM_config .indent40 { margin-left: 40%; }",
                "#GM_config .field_label { font-size: 12px; font-weight: bold; margin-right: 6px; }",
                "#GM_config .radio_label { font-size: 12px; }",
                "#GM_config .block { display: block; }",
                "#GM_config .saveclose_buttons { margin: 16px 10px 10px; padding: 2px 12px; }",
                "#GM_config .reset, #GM_config .reset a," +
                " #GM_config_buttons_holder { color: #000; text-align: right; }",
                "#GM_config .config_header { font-size: 20pt; margin: 0; }",
                "#GM_config .config_desc, #GM_config .section_desc, #GM_config .reset { font-size: 9pt; }",
                "#GM_config .center { text-align: center; }",
                "#GM_config .section_header_holder { margin-top: 8px; }",
                "#GM_config .config_var { margin: 0 0 4px; }",
                "#GM_config .section_header { background: #414141; border: 1px solid #000; color: #FFF;",
                " font-size: 13pt; margin: 0; }",
                "#GM_config .section_desc { background: #EFEFEF; border: 1px solid #CCC; color: #575757;" +
                " font-size: 9pt; margin: 0 0 6px; }"
            ].join('\n') + '\n',
            basicPrefix: "GM_config",
            stylish: ""
        };
    }

    if (args.length == 1 &&
        typeof args[0].id == "string" &&
        typeof args[0].appendChild != "function") var settings = args[0];
    else {
        // Provide backwards-compatibility with argument style intialization
        var settings = {};

        // loop through GM_config.init() arguments
        for (var i = 0, l = args.length, arg; i < l; ++i) {
            arg = args[i];

            // An element to use as the config window
            if (typeof arg.appendChild == "function") {
                settings.frame = arg;
                continue;
            }

            switch (typeof arg) {
            case 'object':
                for (var j in arg) { // could be a callback functions or settings object
                    if (typeof arg[j] != "function") { // we are in the settings object
                        settings.fields = arg; // store settings object
                        break; // leave the loop
                    } // otherwise it must be a callback function
                    if (!settings.events) settings.events = {};
                    settings.events[j] = arg[j];
                }
                break;
            case 'function': // passing a bare function is set to open callback
                settings.events = {
                    onOpen: arg
                };
                break;
            case 'string': // could be custom CSS or the title string
                if (/\w+\s*\{\s*\w+\s*:\s*\w+[\s|\S]*\}/.test(arg))
                    settings.css = arg;
                else
                    settings.title = arg;
                break;
            }
        }
    }

    /* Initialize everything using the new settings object */
    // Set the id
    if (settings.id) config.id = settings.id;
    else if (typeof config.id == "undefined") config.id = 'GM_config';

    // Set the title
    if (settings.title) config.title = settings.title;

    // Set the custom css
    if (settings.css) config.css.stylish = settings.css;

    // Set the frame
    if (settings.frame) config.frame = settings.frame;

    // Set the event callbacks
    if (settings.events) {
        var events = settings.events;
        for (e in events)
            config["on" + e.charAt(0).toUpperCase() + e.slice(1)] = events[e];
    }

    // Create the fields
    if (settings.fields) {
        var stored = config.read(), // read the stored settings
            fields = settings.fields,
            customTypes = settings.types || {},
            field;
        if (fields instanceof Array) {
            fields.sort(compareField);
            fields = removeSectionDuplicates(fields);

            for (var h = 0; h < fields.length; h += 1) {
                field = fields[h];
                // for each field definition create a field object
                if (field) {
                    config.fields[field.id] = new GM_configField(field, stored[field.id], field.id,
                        customTypes[field.type]);
                } else if (config.fields[field.id]) {
                    delete config.fields[field.id];
                }
            }
        } else {
            for (var id in fields) {
                field = fields[id];
                // for each field definition create a field object
                if (field) {
                    config.fields[id] = new GM_configField(field, stored[id], id,
                        customTypes[field.type]);
                } else if (config.fields[id]) {
                    delete config.fields[id];
                }
            }
        }
    }

    // If the id has changed we must modify the default style
    if (config.id != config.css.basicPrefix) {
        config.css.basic = config.css.basic.replace(
            new RegExp('#' + config.css.basicPrefix, 'gm'), '#' + config.id);
        config.css.basicPrefix = config.id;
    }
    config.backupValues = {};
    for (var f in config.fields) {
        if (config.fields.hasOwnProperty(f)) {
            config.backupValues[f] = config.fields[f].value;
        }
    }
}

GM_configStruct.prototype = {
    // Support old method of initalizing
    init: function () {
        GM_configInit(this, arguments);
        this.onInit();
    },

    // call GM_config.open() from your script to open the menu
    open: function () {
        // Die if the menu is already open on this page
        // You can have multiple instances but you can't open the same instance twice
        var match = document.getElementById(this.id);
        if (match && (match.tagName == "IFRAME" || match.childNodes.length > 0)) return;

        // Sometimes "this" gets overwritten so create an alias
        var config = this;

        // Function to build the mighty config window :)
        function buildConfigWin(body, head) {
            var create = config.create,
                fields = config.fields,
                configId = config.id,
                bodyWrapper = create('div', {
                    id: configId + '_wrapper'
                });

            // Append the style which is our default style plus the user style
            head.appendChild(
                create('style', {
                    type: 'text/css',
                    textContent: config.css.basic + config.css.stylish
                }));

            // Add header and title
            bodyWrapper.appendChild(create('div', {
                id: configId + '_header',
                className: 'config_header block center'
            }, config.title));

            // Append elements
            var section = bodyWrapper,
                secNum = 0; // Section count

            // loop through fields
            for (var id in fields) {
                var field = fields[id],
                    settings = field.settings;

                if (settings.section) { // the start of a new section
                    section = bodyWrapper.appendChild(create('div', {
                        className: 'section_header_holder',
                        id: configId + '_section_' + secNum
                    }));

                    if (Object.prototype.toString.call(settings.section) !== '[object Array]')
                        settings.section = [settings.section];

                    if (settings.section[0])
                        section.appendChild(create('div', {
                            className: 'section_header center',
                            id: configId + '_section_header_' + secNum
                        }, settings.section[0]));

                    if (settings.section[1])
                        section.appendChild(create('p', {
                            className: 'section_desc center',
                            id: configId + '_section_desc_' + secNum
                        }, settings.section[1]));
                    ++secNum;
                }

                // Create field elements and append to current section
                section.appendChild((field.wrapper = field.toNode(configId)));
            }

            // Add save and close buttons
            bodyWrapper.appendChild(create('div', {
                    id: configId + '_buttons_holder'
                },

                create('button', {
                    id: configId + '_saveBtn',
                    textContent: 'Save',
                    title: 'Save settings',
                    className: 'saveclose_buttons',
                    onclick: function () {
                        config.save()
                    }
                }),

                create('button', {
                    id: configId + '_closeBtn',
                    textContent: 'Close',
                    title: 'Close window',
                    className: 'saveclose_buttons',
                    onclick: function () {
                        config.close()
                    }
                }),

                create('div', {
                        className: 'reset_holder block'
                    },

                    // Reset link
                    create('a', {
                        id: configId + '_resetLink',
                        textContent: 'Reset to defaults',
                        href: '#',
                        title: 'Reset fields to default values',
                        className: 'reset',
                        onclick: function (e) {
                            e.preventDefault();
                            config.reset()
                        }
                    })
                )));

            body.appendChild(bodyWrapper); // Paint everything to window at once
            config.center(); // Show and center iframe
            window.addEventListener('resize', config.center, false); // Center frame on resize

            // Call the open() callback function
            config.onOpen(config.frame.contentDocument || config.frame.ownerDocument,
                config.frame.contentWindow || window,
                config.frame);

            // Close frame on window close
            window.addEventListener('beforeunload', function () {
                config.close();
            }, false);

            // Now that everything is loaded, make it visible
            config.frame.style.display = "block";
            config.isOpen = true;
        }

        // Change this in the onOpen callback using this.frame.setAttribute('style', '')
        var defaultStyle = 'bottom: auto; border: 1px solid #000; display: none; height: 75%;' + ' left: 0; margin: 0; max-height: 95%; max-width: 95%; opacity: 0;' + ' overflow: auto; padding: 0; position: fixed; right: auto; top: 0;' + ' width: 75%; z-index: 999;';

        // Either use the element passed to init() or create an iframe
        if (this.frame) {
            this.frame.id = this.id; // Allows for prefixing styles with the config id
            this.frame.setAttribute('style', defaultStyle);
            buildConfigWin(this.frame, this.frame.ownerDocument.getElementsByTagName('head')[0]);
        } else {
            // Create frame
            document.body.appendChild((this.frame = this.create('iframe', {
                id: this.id,
                style: defaultStyle
            })));

            // In WebKit src can't be set until it is added to the page
            this.frame.src = 'about:blank';
            // we wait for the iframe to load before we can modify it
            this.frame.addEventListener('load', function (e) {
                var frame = config.frame;
                var body = frame.contentDocument.getElementsByTagName('body')[0];
                body.id = config.id; // Allows for prefixing styles with the config id
                buildConfigWin(body, frame.contentDocument.getElementsByTagName('head')[0]);
            }, false);
        }
    },

    save: function () {
        var forgotten = this.write(),
            changes;
        this.onSave(forgotten); // Call the save() callback function

        //check for changed values since the last save
        for (var f in this.fields) {
            if (this.fields.hasOwnProperty(f)) {
                if (this.backupValues[f] !== this.fields[f].value) {
                    changes = changes || {};
                    changes[f] = {
                        'old': this.backupValues[f],
                        'new': this.fields[f].value
                    };
                }
            }
        }

        //fire onChange event and reset the backup
        if (changes) {
            this.onChange(changes);
            this.backupValues = {};
            for (var f in this.fields) {
                if (this.fields.hasOwnProperty(f)) {
                    this.backupValues[f] = this.fields[f].value;
                }
            }
        }
    },

    close: function () {
        // If frame is an iframe then remove it
        if (this.frame.contentDocument) {
            this.remove(this.frame);
            this.frame = null;
        } else { // else wipe its content
            this.frame.innerHTML = "";
            this.frame.style.display = "none";
        }

        // Null out all the fields so we don't leak memory
        var fields = this.fields;
        for (var id in fields) {
            var field = fields[id];
            field.wrapper = null;
            field.node = null;
        }

        this.onClose(); //  Call the close() callback function
        this.isOpen = false;
    },

    set: function (name, val) {
        this.fields[name].value = val;
    },

    get: function (name, fallback) {
        //return fallback value when field doesn't exist
        if (this.fields.hasOwnProperty(name)){
            return this.fields[name].value;
        }
        return fallback;
    },

    write: function (store, obj) {
        if (!obj) {
            var values = {},
                forgotten = {},
                fields = this.fields;

            for (var id in fields) {
                var field = fields[id];
                var value = field.toValue();

                if (field.save) {
                    if (value != null) {
                        values[id] = value;
                        field.value = value;
                    } else
                        values[id] = field.value;
                } else
                    forgotten[id] = value;
            }
        }
        try {
            this.setValue(store || this.id, this.stringify(obj || values));
        } catch (e) {
            this.log("GM_config failed to save settings!");
        }

        return forgotten;
    },

    read: function (store) {
        try {
            var rval = this.parser(this.getValue(store || this.id, '{}'));
        } catch (e) {
            this.log("GM_config failed to read saved settings!");
            var rval = {};
        }
        return rval;
    },

    reset: function () {
        var fields = this.fields;

        // Reset all the fields
        for (var id in fields) fields[id].reset();

        this.onReset(); // Call the reset() callback function
    },

    create: function () {
        switch (arguments.length) {
        case 1:
            var A = document.createTextNode(arguments[0]);
            break;
        default:
            var A = document.createElement(arguments[0]),
                B = arguments[1];
            for (var b in B) {
                if (b.indexOf("on") == 0)
                    A.addEventListener(b.substring(2), B[b], false);
                else if (",style,accesskey,id,name,src,href,which,for".indexOf("," +
                        b.toLowerCase()) != -1)
                    A.setAttribute(b, B[b]);
                else
                    A[b] = B[b];
            }
            if (typeof arguments[2] == "string")
                A.innerHTML = arguments[2];
            else
                for (var i = 2, len = arguments.length; i < len; ++i)
                    A.appendChild(arguments[i]);
        }
        return A;
    },

    center: function () {
        var node = this.frame;
        if (!node) return;
        var style = node.style,
            beforeOpacity = style.opacity;
        if (style.display == 'none') style.opacity = '0';
        style.display = '';
        style.top = Math.floor((window.innerHeight / 2) - (node.offsetHeight / 2)) + 'px';
        style.left = Math.floor((window.innerWidth / 2) - (node.offsetWidth / 2)) + 'px';
        style.opacity = '1';
    },

    remove: function (el) {
        if (el && el.parentNode) el.parentNode.removeChild(el);
    }
};

// Define a bunch of API stuff
(function () {
    var isGM = typeof GM_getValue != 'undefined' &&
        typeof GM_getValue('a', 'b') != 'undefined',
        setValue, getValue, stringify, parser;

    // Define value storing and reading API
    if (!isGM) {
        setValue = function (name, value) {
            return localStorage.setItem(name, value);
        };
        getValue = function (name, def) {
            var s = localStorage.getItem(name);
            return s == null ? def : s
        };

        // We only support JSON parser outside GM
        stringify = JSON.stringify;
        parser = JSON.parse;
    } else {
        setValue = GM_setValue;
        getValue = GM_getValue;
        stringify = typeof JSON == "undefined" ?
            function (obj) {
                return obj.toSource();
            } : JSON.stringify;
        parser = typeof JSON == "undefined" ?
            function (jsonData) {
                return (new Function('return ' + jsonData + ';'))();
            } : JSON.parse;
    }

    GM_configStruct.prototype.isGM = isGM;
    GM_configStruct.prototype.setValue = setValue;
    GM_configStruct.prototype.getValue = getValue;
    GM_configStruct.prototype.stringify = stringify;
    GM_configStruct.prototype.parser = parser;
    GM_configStruct.prototype.log = window.console ?
        console.log : (isGM && typeof GM_log != 'undefined' ?
            GM_log : (window.opera ?
                opera.postError : function () { /* no logging */ }
            ));
})();

function GM_configDefaultValue(type, options) {
    var value;

    if (type.indexOf('unsigned ') == 0)
        type = type.substring(9);

    switch (type) {
    case 'radio':
    case 'select':
        value = options[0];
        break;
    case 'checkbox':
        value = false;
        break;
    case 'int':
    case 'integer':
    case 'float':
    case 'number':
        value = 0;
        break;
    default:
        value = '';
    }

    return value;
}

function GM_configField(settings, stored, id, customType) {
    // Store the field's settings
    this.settings = settings;
    this.id = id;
    this.node = null;
    this.wrapper = null;
    this.save = typeof settings.save == "undefined" ? true : settings.save;

    // Buttons are static and don't have a stored value
    if (settings.type == "button") this.save = false;

    // if a default value wasn't passed through init() then
    //   if the type is custom use its default value
    //   else use default value for type
    // else use the default value passed through init()
    this['default'] = typeof settings['default'] == "undefined" ?
        customType ?
        customType['default'] : GM_configDefaultValue(settings.type, settings.options) : settings['default'];

    // Store the field's value
    this.value = typeof stored == "undefined" ? this['default'] : stored;

    // Setup methods for a custom type
    if (customType) {
        this.toNode = customType.toNode;
        this.toValue = customType.toValue;
        this.reset = customType.reset;
    }
}

GM_configField.prototype = {
    create: GM_configStruct.prototype.create,

    toNode: function (configId) {
        var field = this.settings,
            value = this.value,
            options = field.options,
            type = field.type,
            id = this.id,
            labelPos = field.labelPos,
            create = this.create;

        function addLabel(pos, labelEl, parentNode, beforeEl) {
            if (!beforeEl) beforeEl = parentNode.firstChild;
            switch (pos) {
            case 'right':
            case 'below':
                if (pos == 'below')
                    parentNode.appendChild(create('br', {}));
                parentNode.appendChild(labelEl);
                break;
            default:
                if (pos == 'above')
                    parentNode.insertBefore(create('br', {}), beforeEl);
                parentNode.insertBefore(labelEl, beforeEl);
            }
        }

        var retNode = create('div', {
                className: 'config_var',
                id: configId + '_' + id + '_var',
                title: field.title || ''
            }),
            firstProp;

        // Retrieve the first prop
        for (var i in field) {
            firstProp = i;
            break;
        }

        var label = field.label && type != "button" ?
            create('label', {
                id: configId + '_' + id + '_field_label',
                for: configId + '_field_' + id,
                className: 'field_label'
            }, field.label) : null;

        switch (type) {
        case 'textarea':
            retNode.appendChild((this.node = create('textarea', {
                innerHTML: value,
                id: configId + '_field_' + id,
                className: 'block',
                cols: (field.cols ? field.cols : 20),
                rows: (field.rows ? field.rows : 2)
            })));
            break;
        case 'radio':
            var wrap = create('div', {
                id: configId + '_field_' + id
            });
            this.node = wrap;

            for (var i = 0, len = options.length; i < len; ++i) {
                var radLabel = create('label', {
                    className: 'radio_label'
                }, options[i]);

                var rad = wrap.appendChild(create('input', {
                    value: options[i],
                    type: 'radio',
                    name: id,
                    checked: options[i] == value
                }));

                var radLabelPos = labelPos &&
                    (labelPos == 'left' || labelPos == 'right') ?
                    labelPos : firstProp == 'options' ? 'left' : 'right';

                addLabel(radLabelPos, radLabel, wrap, rad);
            }

            retNode.appendChild(wrap);
            break;
        case 'select':
            var wrap = create('select', {
                id: configId + '_field_' + id
            });
            this.node = wrap;

            for (var i = 0, len = options.length; i < len; ++i) {
                var option = options[i];
                wrap.appendChild(create('option', {
                    value: option,
                    selected: option == value
                }, option));
            }

            retNode.appendChild(wrap);
            break;
        default: // fields using input elements
            var props = {
                id: configId + '_field_' + id,
                type: type,
                value: type == 'button' ? field.label : value
            };

            switch (type) {
            case 'checkbox':
                props.checked = value;
                break;
            case 'button':
                props.size = field.size ? field.size : 25;
                if (field.script) field.click = field.script;
                if (field.click) props.onclick = field.click;
                break;
            case 'hidden':
                break;
            default:
                // type = text, int, or float
                props.type = 'text';
                props.size = field.size ? field.size : 25;
            }

            retNode.appendChild((this.node = create('input', props)));
        }

        if (label) {
            // If the label is passed first, insert it before the field
            // else insert it after
            if (!labelPos)
                labelPos = firstProp == "label" || type == "radio" ?
                "left" : "right";

            addLabel(labelPos, label, retNode);
        }

        return retNode;
    },

    toValue: function () {
        var node = this.node,
            field = this.settings,
            type = field.type,
            unsigned = false,
            rval = null;

        if (!node) return rval;

        if (type.indexOf('unsigned ') == 0) {
            type = type.substring(9);
            unsigned = true;
        }

        switch (type) {
        case 'checkbox':
            rval = node.checked;
            break;
        case 'select':
            rval = node[node.selectedIndex].value;
            break;
        case 'radio':
            var radios = node.getElementsByTagName('input');
            for (var i = 0, len = radios.length; i < len; ++i)
                if (radios[i].checked)
                    rval = radios[i].value;
            break;
        case 'button':
            break;
        case 'int':
        case 'integer':
        case 'float':
        case 'number':
            var num = Number(node.value);
            var warn = 'Field labeled "' + field.label + '" expects a' +
                (unsigned ? ' positive ' : 'n ') + 'integer value';

            if (isNaN(num) || (type.substr(0, 3) == 'int' &&
                    Math.ceil(num) != Math.floor(num)) ||
                (unsigned && num < 0)) {
                alert(warn + '.');
                return null;
            }

            if (!this._checkNumberRange(num, warn))
                return null;
            rval = num;
            break;
        default:
            rval = node.value;
            break;
        }

        return rval; // value read successfully
    },

    reset: function () {
        var node = this.node,
            field = this.settings,
            type = field.type;

        if (!node) return;

        switch (type) {
        case 'checkbox':
            node.checked = this['default'];
            break;
        case 'select':
            for (var i = 0, len = node.options.length; i < len; ++i)
                if (node.options[i].textContent == this['default'])
                    node.selectedIndex = i;
            break;
        case 'radio':
            var radios = node.getElementsByTagName('input');
            for (var i = 0, len = radios.length; i < len; ++i)
                if (radios[i].value == this['default'])
                    radios[i].checked = true;
            break;
        case 'button':
            break;
        default:
            node.value = this['default'];
            break;
        }
    },

    remove: function (el) {
        GM_configStruct.prototype.remove(el || this.wrapper);
        this.wrapper = null;
        this.node = null;
    },

    reload: function () {
        var wrapper = this.wrapper;
        if (wrapper) {
            var fieldParent = wrapper.parentNode;
            fieldParent.insertBefore((this.wrapper = this.toNode()), wrapper);
            this.remove(wrapper);
        }
    },

    _checkNumberRange: function (num, warn) {
        var field = this.settings;
        if (typeof field.min == "number" && num < field.min) {
            alert(warn + ' greater than or equal to ' + field.min + '.');
            return null;
        }

        if (typeof field.max == "number" && num > field.max) {
            alert(warn + ' less than or equal to ' + field.max + '.');
            return null;
        }
        return true;
    }
};

// Create default instance of GM_config
var GM_config = new GM_configStruct();