Source: org/terraswarm/accessor/accessors/web/hosts/browser/browser.js

// Browser host implementation of host-specific functions.
//
// Copyright (c) 2015-2017 The Regents of the University of California.
// All rights reserved.
//
// Permission is hereby granted, without written agreement and without
// license or royalty fees, to use, copy, modify, and distribute this
// software and its documentation for any purpose, provided that the above
// copyright notice and the following two paragraphs appear in all copies
// of this software.
//
// IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
// ENHANCEMENTS, OR MODIFICATIONS.
//

/** Browser host implementation of host-specific functions.
 *  This module provides functions to generate web content for accessors.
 *  It relies on the /common/commonHost.js module, which it loads when needed.
 *
 *  To generate an interactive instance of an accessor, where you can provide
 *  input and parameter values into entry boxes and cause the accessor to react,
 *  Include in the head section of your web page the following:
 *
 *     <script src="/accessors/hosts/browser/browser.js"></script>
 *
 *  In the body of a web page, you can instantiate an accessor by creating an
 *  HTML element with class "accessor" and specifying a source, which is the fully
 *  qualified accessor name.  For example:
 *
 *     <div class="accessor" src="net/REST" id="REST"></div>
 *
 *  The id attribute can have whatever value you like, but it must be unique on
 *  the web page.
 *
 *  You can also create a directory of accessors by including in your document
 *  an element with class "accessorDirectory". For example:
 *
 *     <div class="accessorDirectory"></div>
 *
 *  This will provide a hierarchical expandable list of accessors that this host
 *  can instantiate.  In addition, if your document has an element with id equal to
 *  "accessorDirectoryTarget", then clicking on an accessor in the directory will
 *  cause that target to be filled with an instance of the accessor, similar to the
 *  one above with class "accessor".  For example,
 *
 *     <div class="accessorDirectoryTarget"></div>
 *
 *  The style of the generated web pages can be customized using CSS. A default
 *  style is achieved by including in the head section of your document the following:
 *
 *     <link rel="stylesheet" type="text/css" href="/accessors/hosts/browser/accessorStyle.css">
 *
 *  The main entry point to this module is the generate() function, which is
 *  invoked when the web page DOM content has been loaded.
 *
 *  @module @accessor-hosts/browser
 *  @author Edward A. Lee, Elizabeth Osyk
 *  @version $$Id$$
 */

// Stop extra messages from jslint and jshint.  Note that there should
// be no space between the / and the * and global. See
// https://chess.eecs.berkeley.edu/ptexternal/wiki/Main/JSHint */
// initialValues is provided by HTML pages that desire initial form field values
// that are different from a particular accessor's default values
/*globals alert, clearTimeout, console, document, Event, initialValues, Promise, setTimeout, window, XMLHttpRequest */
/*jshint globalstrict: true, multistr: true */

'use strict';

///////////////////////////////////////////////////////////////////
//// Web page setup

// Only add event listeners the first time the library is loaded.
// (Otherwise, duplicate event listeners will generate duplicate accessor HTML).

if (!window.hasOwnProperty('browserJSLoaded')) {

    // Note that the following will not work in IE 8 or older.
    window.addEventListener('DOMContentLoaded', function () {
        window.generate();
    });

    window.addEventListener('unload', function (event) {
        if (window.accessors) {
            for (var accessor in window.accessors) {
                if (accessor.initialized) {
                    accessor.wrapup();
                }
            }
        }
    });

    window.browserJSLoaded = true;
}

// Check the URL for a querystring specifying an accessor to load (optional).
// This code assumes that "accessor" is the only querystring parameter passed.
// Multiple parameters are not supported.
// The querystring uses . instead of / since / is a special character in URLs. -->
window.onload = function () {
    var url = window.location.href;
    var index = url.lastIndexOf('?');
    var querystring = "",
        accessor = "";
    var slashIndex = -1;

    if (index >= 0) {
        querystring = url.substring(index + 1, url.length);

        // This code assumes that "accessor" is the only querystring parameter passed.
        if (querystring.startsWith("accessor=")) {
            querystring = querystring.substring(9, querystring.length);

            // Querystring uses . instead of / which is a special character.
            // Replace . with /
            querystring = querystring.replace('.', '/');
            
            // Add trailing .js if not present
            if (querystring.indexOf('.js') < 0) {
            	querystring = querystring + '.js';
            }
            generateAccessorHTML(querystring, 'accessorDirectoryTarget');

            // Call toggleVisbility() to expand the directory that this
            // this accessor is located in.  For example, expand "net"
            // for "net/REST".
            slashIndex = querystring.indexOf("/");
            if (slashIndex > 0) {
                toggleVisibility("/accessors/" + querystring.substring(0, slashIndex), 0, getIndex);
            }
        }
    }
};

///////////////////////////////////////////////////////////////////
//// Functions

// Export commonHost after it is loaded.  Used by Test accessor.
var commonHost;

// These will be defined when commonHost.js is loaded.  Used by Test accessor
// and mocha test cases.
var Accessor, instantiate;
var isReifiableBy;

// Needed by computervision module.  The computer vision code did not originally
// use strict mode. Declare variables here to avoid 'undeclared variable' error.
var Module = {};
var i = 0;
var h, w, x, y;

/** Local function controlling how standard elements are rendered in the
 *  document with an optional label.
 *  @param target The target document element id.
 *  @param label The label, or null to not have a label.
 *  @param content The content.
 */
function appendDoc(target, label, content) {
    var pp = document.createElement('p');
    var title = '';
    if (label) {
        title = '<b>' + label + ':</b> ';
    }
    pp.innerHTML = title + content;
    target.appendChild(pp);
}

/** Local function to add a placeholder to later populate.
 *  @param target The target document element id.
 *  @param id The id for the placeholder.
 *  @param element The element type (e.g. 'div', 'pp', or 'span').
 *  @return The placeholder element.
 */
function appendPlaceholder(target, id, element) {
    var pp = document.createElement(element);
    pp.setAttribute('id', id);
    target.appendChild(pp);
    return pp;
}

/** Populate the current page by searching for elements with class 'accessor'
 *  and attribute 'src', generating HTML for the specified accessor, and
 *  inserting that HTML content into the element. Also search for elements
 *  with class 'accessorDirectory' and insert an accessory directory.
 */
function generate() {
    var i, element;
    var accessorCount = 0;
    var acessorElements = document.getElementsByClassName('accessor');
    if (acessorElements && acessorElements.length > 0) {
        for (i = 0; i < acessorElements.length; i++) {
            element = acessorElements[i];
            var src = element.getAttribute('src');
            if (src) {
                accessorCount++;
                var id = element.getAttribute('id');
                if (!id) {
                    // No id. Assign one.
                    id = 'accessor' + accessorCount;
                    element.setAttribute('id', id);
                }
                generateAccessorHTML(src, id);
            }
        }
    }
    var elements = document.getElementsByClassName('accessorDirectory');
    if (elements && elements.length > 0) {
        for (i = 0; i < elements.length; i++) {
            element = elements[i];
            generateAccessorDirectory(element);
        }
    }
}

/** Generate HTML for an accessor defined at the specified path.
 *  If the path is relative (does not begin with '/' or './'), then the accessor
 *  specification will be loaded from the accessor library stored on the host.
 *  If the path is absolute (beginning with '/'), then the accessor specification
 *  will be loaded from the web server providing this swarmlet host at that path.
 *  If the accessor has no inputs, then it will be initialized and fired.
 *  Otherwise, a 'react to inputs' button will appear that will initialize and
 *  fire the actor on command.
 *
 *  As a side effect of invoking this, the window object for the web page
 *  acquires a field ```accessors``` with a property whose name equals the
 *  id argument whose value is the provided accessor
 *  instance with some additional utilities to support the web page.
 *
 *  If there was a previously generated accessor with this same id, and it has
 *  has been initialized, then this
 *  function will invoke its wrapup() function, if it defines one, before
 *  generating the HTML. It will also clear the target element (which has
 *  the same id as the accessor).
 *
 *  Optionally, this method can accept the full text of an accessor via the
 *  text parameter.  This is used to create accessors on-the-fly.  The tutorial
 *  instantiates accessors this way.
 *
 *  @path The path to the accessor.
 *  @param id The id of the accessor, which is also the id of the target element
 *   on the web page into which to insert the generated HTML.
 *  @param text (Optional) The text of the accessor, to be used instead of
 *   retrieving an accessor from the filesystem.
 */
function generateAccessorHTML(path, id, text) {

    // Unless an error occurs or required modules are missing,
    // assume the accessor is executable.
    var executable = true;

    // Cache modules loaded by require().
    var loadedModules = {};

    // Need to ensure the wrapup method of any
    // previous accessor at this target is invoked.
    if (window.accessors) {
        var accessor = window.accessors[id];
        if (accessor) {
            if (accessor.initialized) {
                accessor.wrapup();
            }
        }
    }

    // Clear any previous contents in the target element.
    var target = document.getElementById(id);
    target.innerHTML = '';

    var code;
    if (text !== null && typeof text !== 'undefined') {
        code = text;
    } else {
        code = getAccessorCode(path);
    }

    // Create a header.
    target = document.getElementById(id);
    var h1 = document.createElement('h1');
    h1.setAttribute('id', 'accessorTitle');
    // Extract the class name from the path.
    var className = path;
    if (className.indexOf('/') === 0) {
        className = className.substring(1);
    }
    if (className.indexOf('accessors/') === 0) {
        className = className.substring(10);
    }
    h1.innerHTML = 'Accessor class: ' + className;
    target.appendChild(h1);

    // Create placeholders for the content.
    appendPlaceholder(target, id + 'RevealCode', 'span');
    appendPlaceholder(target, id + 'Error', 'div');
    appendPlaceholder(target, id + 'Base', 'div');
    appendPlaceholder(target, id + 'Modules', 'div');
    var docElement = appendPlaceholder(target, id + 'Documentation', 'p');
    appendPlaceholder(target, id + 'Tables', 'p');

    ///////////////////////////////////////////////////////////////////
    //// Define top-level functions that the accessor might invoke.

    // NOTE: alert(), clearInterval(), clearTimeout(), setInterval(), and
    // setTimeout() are all provided by the browser.
    // FIXME: Reimplement setInterval() and setTimeout() to make them
    // precise for composite accessors.

    // Report an error on the console and on the web page.
    // @param err The error.
    // @param detail Optional context information for the error
    function error(err, detail) {
        console.error(err);
        var pp = document.createElement('p');
        pp.setAttribute('class', 'accessorError');
        if (!detail) {
            detail = 'from accessor';
        }
        pp.innerHTML = 'Error ' +
            detail +
            ' at ' +
            path +
            ': ' +
            err;
        var target = document.getElementById(id + 'Error');
        target.appendChild(pp);
        executable = false;
    }

    // Get data from an input. This implementation assumes that the document
    // has an element with attribute 'id' equal to ```id.name```,
    // where id is the id of this accessor.
    // Such an attribute is created by the generate() function.
    // This implementation also assumes that the window object has a field
    // ```accessors``` with a property whose name equals the id of this accessor
    // whose value is an instance of the Accessor class of the common/commonHost.js
    // module.
    function get(name) {
        return getInputOrParameter(name, 'input', id);
    }

    // Return a resource, which in this implementation just attempts to read the
    // resource using HTTP.
    // @param uri The uri to be read.
    // @param timeout The time to wait before giving up. This defaults to 5000,
    //  5 seconds, if not provided.
    // @return The responseText from the request.
    function getResource(uri, timeout) {
        if (!timeout && timeout !== 0) {
            timeout = 5000;
        }
        
        // Check for a filename-only URL.  E.g., GDPAuthorization.txt
        // In this case, grab the current window URL and check for the file in 
        // the same directory.  
        // Added to support similar behavior to CapeCode, so we can use the same
        // code-generated accessors in CapeCode and browser.
        if (uri.indexOf('/') === -1) {
        	var path = window.location.href;
        	var index = path.lastIndexOf('/');
        	if (index > 0) {
        		path = window.location.href.substring(0, index);
        	}
        	uri = path + "/" + uri;
        }
        
        var request = new XMLHttpRequest();

        // The third argument specifies a synchronous read.
        request.open("GET", uri, false);
        var timeoutHandler = setTimeout(handleTimeout, timeout);

        function handleTimeout() {
            request.abort();
            error("getResource timed out at URI: " + uri);
        }
        // Null argument says there is no body.
        request.send(null);
        clearTimeout(timeoutHandler);
        // readyState === 4 is the same as readyState === request.DONE.
        if (request.readyState === request.DONE) {
            if (request.status <= 400) {
                return request.responseText;
            }
            throw "getResource failed with code " + request.status + " at URL: " + uri;
        }
        throw "getResource did not complete: " + uri;
    }

    // Perform a synchronous HTTP request.
    // @deprecated Use the httpClient module instead.
    // @param url The url.
    // @param method The method to be passed to the XMLHttpRequest.open() call.
    // @param properties Ignored in this implementation
    // @param body The body that is to be sent.  If this argument
    // is null, then no body is sent.
    // @param timeout Ignored in this implementation.
    function httpRequest(url, method, properties, body, timeout) {
        var request = new XMLHttpRequest();
        // The third argument specifies a synchronous read.
        request.open(method, url, false);
        // Null argument says there is no body.
        request.send(body);
        // readyState === 4 is the same as readyState === request.DONE.
        if (request.readyState === request.DONE) {
            if (request.status <= 400) {
                return request.responseText;
            }
            throw "httpRequest failed with code " + request.status + " at URL: " + url;
        }
        throw "httpRequest did not complete: " + url;
    }

    /** Fetch and execute a module or accessor whose functionality is given in JavaScript at
     *  the specified path on the server. The path will be requested from the same server
     *  that served the page executing this script. If no callback function is given,
     *  then a synchronous (blocking) request will be made (best to avoid this in a web page).
     *  If a callback function is given, then after receiving and evaluating the
     *  JavaScript code, the callback function will be invoked.
     *
     *  If the path begins with a '/' or './', then it will be interpreted as the path
     *  to a resource provided by the web server serving this swarmlet host.
     *  Otherwise, it will be interpreted as the name of a module provided by this
     *  swarmlet host.
     *
     *  The returned object includes any properties
     *  that have been added to the 'exports' property in the specified code.
     *  For example, if the module is to export a function, the code
     *  could define the function as follows:</p>
     *
     *  ```javascript
     *   exports.myFunction = function () {...};
     *  ```
     *
     *  Alternatively, the code can explicitly define
     *  the exports object as follows:
     *
     *  ```javascript
     *   var myFunction = function () {...};
     *   module.exports = {
     *       myFunction : myFunction
     *   };
     *  ```
     *
     *  The module can be an accessor. If the module or accessor
     *  fails to load and no callback is given, then an exception will be thrown.
     *  The caller should catch this exception and generate appropriate HTML content.
     *
     *  This implementation is inspired by the requires() function implemented
     *  by Walter Higgins, found here:
     *
     *    https://github.com/walterhiggins/commonjs-modules-javax-script
     *
     *  @param path The code to fetch (a JavaScript file or module name).
     *  @param id The id on the page for which the module is needed.
     *  @param callback The callback function, which gets two arguments: an error
     *   message (or null if the request succeeded) and the response JavaScript text.
     *   If this argument is omitted or null, then the path is retrieved synchronously
     *   and either the JavaScript text will be returned or an exception will be thrown.
     *  @see http://nodejs.org/api/modules.html#modules_the_module_object
     *  @see also: http://wiki.commonjs.org/wiki/Modules
     */
    function loadFromServer(path, id, callback) {

        var evaluate = function (code) {
            // Create the exports object to be populated.
            // Some libraries overwrite module.exports instead of adding to exports.
            var module = {};
            module.exports = {};
            var exports = module.exports;

            // In strict mode, eval() cannot modify the scope of this function.
            // Hence, we wrap the code in the function, and will pass in the
            // exports object that we want the code to modify.
            var wrapper = eval('(function (exports) {' + code + '})');

            // Populate the exports field.
            wrapper(module.exports);

            return module.exports;
        };
        if (callback) {
            // The third argument states that unless the path starts with '/'
            // or './', then the path should be searched for in the modules directory.
            getJavaScript(path, function (err, code) {
                if (err) {
                    callback(err, code);
                } else {
                    try {
                        callback(null, evaluate(code));
                    } catch (err2) {
                        callback(err2, null);
                    }
                }
            }, true);
        } else {
            // Synchronous execution.
            // This could throw an exception.
            // The third argument states that unless the path starts with '/'
            // or './', then the path should be searched for in the modules directory.
            var code = getJavaScript(path, null, true);
            return evaluate(code);
        }
    }

    // Print a message to the console.
    // @param message The message that is passed
    // to console.log().
    function print(message) {
        console.log(message);
    }

    // Synchronously read a URL.
    // @deprecated Use the httpClient module instead.
    // @param url The url to be read
    // @return The responseText from the request.
    function readURL(url) {
        return getResource(url);
    }

    // Load the specified module.
    function require(path) {
        // // FIXME: This is needed so that we can avoid platform
        // // dependent code in commonHost.js, but it is so ugly. -cxh
    	// FIXME:  Beth: This was commented out but I reinstated it as 
    	// the speech recognition demo was crashing.
        if (path === './modules/deterministicTemporalSemantics') {
             return require('/accessors/hosts/common/modules/deterministicTemporalSemantics');
        }

        // If the commonHost is required, return it.
        if (path.indexOf("commonHost") >= 0) {
            return commonHost;
        }
        
        var sawAccessorsModules = false;
        //Delete @accessors-modules/ from path start; it's already accounted for.
        if (path.indexOf("@accessors-modules/") === 0) {
            // require('@accessors-modules/text-display') needs this because
            // text-display is in common/modules.
            sawAccessorsModules = true;
            path = path.substring(19);
        }

        // If module already loaded, return the cached copy.
        if (loadedModules.hasOwnProperty(path)) {
            return (loadedModules[path]);
        } else {
            // Otherwise, load the module.
            // Indicate required modules in the docs.
            var modules = document.getElementById(id + 'Modules');
            var text = modules.innerHTML;
            // No need to mention deterministicTemporalSemantics in the list of modules.
            if (path.indexOf('deterministicTemporalSemantics') !== -1) {
                if (text) {
                    // Remove the trailing '</p>'
                    text = text.replace('</p>', '');
                }
            } else {
                if (!text) {
                    text = '<p><b>Modules required:</b> ' + path;
                } else {
                    // Remove the trailing '</p>'
                    text = text.replace('</p>', '');
                    text += ', ' + path;
                }
            }
            // Default return value.
            var result = 'Module failed to load';
            // Load the module synchronously because the calling function needs the returned
            // value. If a module fails to load, however, we will still want to display a web
            // page. It's just that execution will fail.
            try {
                // The third argument (null) indicates synchronous load.
                result = loadFromServer(path, id, null);
                loadedModules[path] = result;
                // If successful, add the module name to the text of the modules list.
            } catch (err) {
                text += '<span class="accessorError"> (Not supported by this host)';
                // If the path includes modules, try common/modules (once).
	                var newPath;
	                if (path.indexOf('common/modules') < 0) {
		                try {
			                    // @accessors-modules/text-display.js needs this.
			                    if (sawAccessorsModules) {
			                        newPath = '/accessors/hosts/common/modules/' + path;
			                        return require(newPath);
			                    }
			                    var index = path.indexOf('modules');
			                    if (index !== -1) {
			                        newPath = '/accessors/hosts/common/' +
			                            path.substr(index);
			                        return require(newPath);
			                    }
			                } catch (err2) {
			                    // Ignore and report original error.
			                    text += '(Also tried ' + newPath + ')';
			                }
	                }
                text += '</span>';
                executable = false;
            }
            modules.innerHTML = text + '</p>';
            return result;
        }
    }

    // Send an output or to an input.  This implementation assumes that the
    // document has an element with attribute 'id' equal to ```id.name```, where
    // id is the id of the accessor and name is the name of the output.
    // Such an attribute is created by the generate() function.
    // This implementation also assumes that the window object has a field
    // ```accessors``` that contains a property with name equal to the
    // whose value is an instance of the Accessor class of the
    // common/commonHost.js module.
    function send(name, value) {
        var isInput = false;
        var element = document.getElementById(id + '.' + name);
        if (!element) {
            alert('No output named ' + name + ' for accessor with id ' + id);
            return;
        }
        // Handle data types.  Check output ports.
        var options = window.accessors[id].outputs[name];
        if (!options) {
            // Check input ports
            options = window.accessors[id].inputs[name];
            isInput = true;
            if (!options) {
                // This could only occur is somehow the document has an element
                // with the right name, but there is no such input.
                alert('No record of output or input named ' + name +
                    ' for accessor with id ' + id);
                return null;
            }
        }
        options.latestValue = value;


        // Set value for HTML element.  Call provideInput() on inputs.
        if (isInput) {
            if (options.type === 'string') {
                element.setAttribute("value", value);
            } else {
                element.setAttribute("value", JSON.stringify(value));
            }
            provideInput(id, name, value);
        } else {
            if (options.type === 'string') {
                element.textContent = value;
            } else {
                element.textContent = JSON.stringify(value);
            }
        }
    }

    var util = require('util');

    ///////////////////////////////////////////////////////////////////
    //// Instantiate the accessor and generate page contents.

    // Next, load and evaluate the accessor code to invoke the setup() function, which
    // will determine what the parameters, inputs, and outputs are, and will set
    // up the accessor to be executed.

    // Load common/commonHost.js code asynchronously.
    loadFromServer('/accessors/hosts/common/commonHost.js',
        id,
        function (err, theCommonHost) {
            var instance;
            if (err) {
                error(err, 'loading commonHost.js');
                return;
            } else {
                // Function bindings for the accessor:
                // We will bind getParameter() later.
                // The browser's getParameter() retrieves values from the HTML page.
                // However, an accessor might call getParameter() in setup()
                // before the page has been created.  In this case, we want to
                // get whatever value the accessor has provided in setup().
                var bindings = {
                    'error': error,
                    'get': get,
                    'getResource': getResource,
                    'httpRequest': httpRequest,
                    'readURL': readURL,
                    'require': require,
                    'send': send,
                    'util': util
                };
                try {
                    // Make the commonHost globally visible.  Used by Test accessor.
                    commonHost = theCommonHost;
                    Accessor = commonHost.Accessor;

                    // Make isReifiableBy globally visible
                    isReifiableBy = commonHost.isReifiableBy;

                    // Override commonHost's require with browser's require.
                    // 'Accessor' constructor is used in Mocha tests.
                    commonHost.Accessor.prototype.require = require;
                    
                    // Needed for trusted accessors to call getTopLevelAccessors().
                    commonHost.allowTrustedAccessors(true);

                    instance = new commonHost.Accessor(
                        className, code, getAccessorCode, bindings);

                    // Mocha tests use instantiate.  Define it.
                    // code = getAccessorCode('net/REST');
                    // instance = new commonHost.Accessor('REST', code);
                    instantiate = function (className, path) {
                        code = getAccessorCode(path);
                        return new commonHost.Accessor(className, code);
                    };
                } catch (err2) {
                    error(err2, 'instantiating accessor');
                    executable = false;
                    // Failed to instantiate the accessor. Can't generate very much.
                    // Generate docs and button to view the accessor code.
                    generateAccessorDocumentation(path, id);
                    generateAccessorCodeElement(code, id);
                    return;
                }
                // If an error occurred or a module was found missing during instantiation,
                // then executable will be false.
                instance.executable = executable;
            }
            // Record the accessor instance.
            // The following will define a global variable 'accessors'
            // if it is not already defined.
            if (!window.accessors) {
                window.accessors = {};
            }
            window.accessors[id] = instance;

            // Create documentation for the accessor.
            generateAccessorDocumentation(path, id);

            // Create a button to view the accessor code.
            generateAccessorCodeElement(code, id);

            // Generate tables for the accessor.
            // getParameter() is overriden here.
            generateTables(instance, id);

            // If the accessor has no inputs, then there will be no
            // 'react to inputs' button. In this case, attempt to initialize
            // and fire accessor.
            if (instance && (!instance.inputList || instance.inputList.length === 0)) {
                reactIfExecutable(id, true);
            }
        });
}

/** Generate a button that will optionally reveal the accessor source code.
 *  @param code The code.
 *  @param id The ID of the accessor.
 */
function generateAccessorCodeElement(code, id) {
    var target = document.getElementById(id + 'RevealCode');

    var button = document.createElement('button');
    button.setAttribute('class', 'accessorButton ui-btn ui-corner-all');
    button.innerHTML = 'reveal code';
    button.id = 'revealCode';
    button.onclick = function () {
        if (button.innerHTML === 'hide code') {
            pre.style.display = 'none';
            button.innerHTML = 'reveal code';
        } else {
            pre.style.display = 'block';
            button.innerHTML = 'hide code';
        }
    };
    target.appendChild(button);

    // Include Google's pretty printer, if possible.
    var script = document.createElement('script');
    script.src = "https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js";
    target.appendChild(script);

    var pre = document.createElement('pre');
    // Class prettyprint invokes Google's pretty printer, if available.
    pre.setAttribute('class', 'prettyprint');
    // Start out hidden.
    pre.style.display = 'none';
    target.appendChild(pre);

    var codeElement = document.createElement('code');
    pre.appendChild(codeElement);
    // Need to escape HTML markup. It is sufficient to just escape <
    codeElement.innerHTML = code.replace(/</g, '&lt;');
}

/** Generate a directory of accessors and place into the specified page element.
 *  If the document has an element with id equal to "accessorDirectoryTarget", then
 *  clicking on an accessor in the directory will cause that target to be filled with
 *  the accessor HTML.
 *  @param element The document element into which to place the directory.
 */
function generateAccessorDirectory(element) {
    // Unfortunately, on some websites, this function may be called more than once.
    // The DOM loaded event occurs more than once.
    // If populating has already been requested, therefore, just return.
    // Note that the populating may not yet have completed.
    if (element.populating) {
        return;
    }
    element.populating = true;


    // Fetch the top-level index.json file.
    getIndex('/accessors/', element, 0);
}

/** Generate documentation for the accessor. This looks for a
 *  a PtDoc file for the accessor, then it uses that to build a documentation
 *  data structure.
 *  @param path The fully qualified class name of the accessor.
 *  @param id The id of the accessor.
 */
function generateAccessorDocumentation(path, id) {
    var i;

    // Attempt to read the PtDoc file.
    path = normalizePath(path);
    path = path + 'PtDoc.xml';

    var request = new XMLHttpRequest();
    request.overrideMimeType("application/xml");
    // FIXME: Have to do a synchronous fetch here with this design because the
    // data structure being constructed will be used when this return.
    request.open('GET', path, false); // Pass false for synchronous
    request.send();
    // Data structure to populate with doc information.
    var docs = {};
    var target = document.getElementById(id + 'Documentation');

    // Read docs from base class and implemented interfaces to provide
    // defaults, but omit the description, author, and version from
    // those. Make sure the instance exists before getting this info.
    if (window.accessors && window.accessors[id]) {
        var implemented = window.accessors[id].implementedInterfaces;
        for (i = 0; i < implemented.length; i++) {
            appendDoc(target, 'Implements', implemented[i].accessorClass);
            getBaseDocumentation(docs, implemented[i].accessorClass);
        }

        if (window.accessors[id].extending) {
            appendDoc(target, 'Extends', window.accessors[id].extending.accessorClass);
            getBaseDocumentation(docs, window.accessors[id].extending.accessorClass);
        }
    }

    // If the request was unsuccessful.
    if (request.status !== 200) {
        docs.description =
            '<p class="accessorWarning">No documentation found for \
                the accessor (tried ' + path + ').';
    } else {
        // Request was successful. Parsed DOM is in responseXML.
        var properties = request.responseXML.getElementsByTagName('property');
        for (i = 0; i < properties.length; i++) {
            var property = properties[i];
            var name = property.getAttribute('name');
            docs[name] = property.getAttribute('value');
        }
    }
    // Now write contents to the web page.
    if (docs.description) {
        appendDoc(target, null, docs.description);
    }
    if (docs.author) {
        appendDoc(target, 'Author', docs.author);
    }
    if (docs.version) {
        appendDoc(target, 'Version', docs.version);
    }
    // Record the docs for future use in generating input/output/parameter
    // documentation.
    if (!window.accessorDocs) {
        window.accessorDocs = {};
    }
    window.accessorDocs[id] = docs;
}

/** Generate parameter, input, and output tables for
 *  the specified accessor instance and insert it
 *  into the element on the page with the specified id.
 *  Also generate a list of contained accessors, if there are any.
 *  @param accessor An accessor instance created by common/commonHost.js.
 *  @param id The id of the accessor.
 */
function generateTables(instance, id) {

    // Declare getParameter() here so we can override accessor's getParameter()
    // just after HTML page has been created.
    // Get data from a parameter. This implementation assumes that the document
    // has an element with attribute 'id' equal to ```id.name```.
    // Such an attribute is created by the generate() function.
    // This implementation also assumes that the window object has a field
    // ```accessors``` with a property whose name matches id
    // whose value is an instance of the Accessor class of the common/commonHost.js
    // module.
    function getParameter(name) {
        return getInputOrParameter(name, 'parameter', id);
    }

    var promises = [];

    // Generate a table for parameters.
    if (instance.parameterList && instance.parameterList.length > 0) {
        promises.push(generateTable("Parameters",
            instance.parameterList, instance.parameters, "parameter", id));
    }
    // Generate a table for inputs.
    if (instance.inputList && instance.inputList.length > 0) {
        promises.push(generateTable("Inputs", instance.inputList, instance.inputs, "input", id));
    }

    // Generate a table for outputs.
    if (instance.outputList && instance.outputList.length > 0) {
        promises.push(generateTable("Outputs", instance.outputList, instance.outputs, "output", id));
    }

    // Generate an event when the table is done.
    // TODO:  It would be even better to generate an event when all content
    // is done.  This would probably require Promises everywhere...
    Promise.all(promises).catch(function(err) {
    	console.log('Error generating accessor tables: ' + err);
    })
    .then(function() {
        window.dispatchEvent(new Event('accessorTableDone'));

        // Override getParameter().
        if (window.accessors[id] !== null &&
            typeof window.accessors[id] !== 'undefined') {
            window.accessors[id].getParameter = getParameter;
        }
    });

    // Generate a list of contained accessors, if any.
    generateListOfContainedAccessors(instance, id);
}

/** Generate a list of accessors contained by the specified accessor instance.
 *  @param instance An accessor instance created by common/commonHost.js.
 *  @param id The id of the accessor.
 */
function generateListOfContainedAccessors(instance, id) {
    var target = document.getElementById(id + 'Tables');
    if (instance.containedAccessors && instance.containedAccessors.length > 0) {
        var header = document.createElement('h2');
        header.innerHTML = 'Contained Accessors';
        target.appendChild(header);

        var list = document.createElement('ol');
        target.appendChild(list);

        for (var i = 0; i < instance.containedAccessors.length; i++) {
            var containedInstance = instance.containedAccessors[i];
            var accessorClass = containedInstance.accessorClass;
            var listElement = document.createElement('li');
            list.appendChild(listElement);
            listElement.innerHTML = 'Instance of: ' + accessorClass;
        }
    }
}

/** Generate a react button.
 *  @param id The id of the accessor.
 */
function generateReactButton(id) {
    var target = document.getElementById(id + 'Tables');
    var targetClass = target.getAttribute('class');
    if (targetClass &&
        (targetClass === 'containedAccessor')) {
        // A contained accessor cannot be asked to react independently.
        return;
    }

    var pp = document.createElement('span');
    var button = document.createElement('button');
    pp.appendChild(button);

    button.innerHTML = 'react to inputs';
    button.setAttribute('class', 'accessorButton ui-btn ui-corner-all');
    button.setAttribute('name', 'react');
    button.setAttribute('type', 'button');
    button.setAttribute('autofocus', 'true');
    button.setAttribute('onclick', 'reactIfExecutable("' + id + '")');
    button.setAttribute('id', 'reactToInputs');

    target.appendChild(pp);
}

/** Generate a table with the specified title and contents and
 *  append it to the element on the page with the specified id.
 *
 *  @param title The title for the table.
 *  @param names A list of field names in the contents object to include, in order.
 *  @param contents An object containing one field for each object to include.
 *  @param role One of 'input', 'output', or 'parameter'.
 *  @param id The id of the accessor.
 */
function generateTable(title, names, contents, role, id) {
    return new Promise(function (resolve, reject) {
        var promises = [];
        var target = document.getElementById(id + 'Tables');

        // Create header line.
        var header = document.createElement('h2');
        header.innerHTML = title;
        header.setAttribute('class', 'accessorTableTitle');
        target.appendChild(header);

        if (role === 'input') {
            // Generate a react button.
            generateReactButton(id);
        }

        var table = document.createElement('table');
        table.setAttribute('class', 'accessorTable ui-responsive table-stroke');
        table.setAttribute('width', '100%');
        table.setAttribute('data-role', 'table');

        var head = document.createElement('thead');
        table.appendChild(head);

        var titleRow = document.createElement('tr');
        titleRow.setAttribute('class', 'accessorTableRow');
        head.appendChild(titleRow);

        var column = document.createElement('th');
        column.setAttribute('class', 'accessorTableHeader');
        // To not expand, use 1%.
        column.setAttribute('width', '1%');
        column.innerHTML = 'Name';
        titleRow.appendChild(column);

        column = document.createElement('th');
        column.setAttribute('class', 'accessorTableHeader');
        column.setAttribute('width', '1%');
        column.innerHTML = 'Type';
        titleRow.appendChild(column);

        column = document.createElement('th');
        column.setAttribute('class', 'accessorTableHeader');
        column.innerHTML = 'Value';
        titleRow.appendChild(column);

        column = document.createElement('th');
        column.setAttribute('class', 'accessorTableHeader');
        column.innerHTML = 'Documentation';
        titleRow.appendChild(column);

        var tbody = document.createElement('tbody');
        table.appendChild(tbody);

        target.appendChild(table);

        var editable = true;
        if (role === 'output') {
            editable = false;
        }
        if (target.getAttribute('class') === 'containedAccessor') {
            editable = false;
        }

        for (var i = 0; i < names.length; i++) {
            var visible = true;
            var item = contents[names[i]];
            if (item) {
                if (item.visibility) {
                    var visibility = item.visibility;
                    if (visibility == 'notEditable') {
                        editable = false;
                    }
                    if (visibility == 'expert') {
                        visible = false;
                    }
                }
                promises.push(generateTableRow(
                    tbody,
                    names[i],
                    id,
                    item,
                    editable,
                    visible,
                    role));
            }
        }

        // Resolve promise once all rows are created.
        Promise.all(promises).then(function () {
            return resolve(true);
        }, function (error) {
            return reject(error);
        });
    });
}

/** Generate a table row for an input, parameter, or output.
 *  Table rows are still created for invisible items so that the content is
 *  available when the accessor is fired.  Invisible rows are tagged with the
 *  class "invisible" to instruct the CSS formatter not to show the row.
 *  @param table The element into which to append the row.
 *  @param name The text to put in the name column.
 *  @param id The id of the accessor.
 *  @param options The options.
 *  @param editable True to make the value an editable input element.
 *  @param visible True to make the table row visible.
 *  @param role Can be parameter, input or output.
 */

function generateTableRow(table, name, id, options, editable, visible, role) {
    return new Promise(function (resolve, reject) {
        var row = document.createElement("tr");
        var classTag;

        if (visible) {
            classTag = "accessorTableRow";
        } else {
            classTag = "accessorTableRow invisible";
        }

        row.setAttribute('class', classTag);

        // Insert the name.
        var nameCell = document.createElement("td");
        nameCell.setAttribute('class', 'accessorTableData');
        nameCell.innerHTML = name;
        row.appendChild(nameCell);

        // Insert the type.
        var typeCell = document.createElement("td");
        typeCell.setAttribute('class', 'accessorTableData');
        var type = options.type;
        if (!type) {
            type = '';
        }
        typeCell.innerHTML = type;
        row.appendChild(typeCell);

        // Insert the value.
        // Initial values are optional. There are two ways to specify initial values.
        // To specify an initial value for all instances of an accessor, define a
        // value in setup().  Please see
        // /net/REST.js for example.
        // To specify an initial value for a web page, add a script element to the
        // page prior to browser.js defining an initialValues object.  Please see
        // /web/hosts/browser/modules/test/httpClient/testREST.html for example.
        var valueCell = document.createElement("td");
        valueCell.setAttribute('class', 'accessorTableData');

        if ((typeof initialValues != "undefined") &&
            (initialValues.hasOwnProperty(id + "." + name))) {
            options.initialValue = initialValues[id + "." + name];
        }

        var value = options.currentValue ||
            options.initialValue || // Page-specific initial value takes precedence
            // over accessor default value (options.value)
            options.value ||
            options.latestOutput ||
            '';

        if (typeof value === 'object') {
            value = JSON.stringify(value);
        }
        if (!editable) {
            valueCell.innerHTML = value;

            // Set a unique ID so that this input can be retrieved by the get()
            // or set by the send() function defined in local.js.
            valueCell.setAttribute('id', id + '.' + name);

        } else {
            // Either a parameter or input.  Outputs are not editable.
            var valueInput;

            if (options.options !== null &&
                typeof options.options !== 'undefined') {
                valueInput = document.createElement('select');

                var selectMe, optionElement;
                
                // Sometimes there is only one option.
                if (typeof options.options === 'string') {
                	var optionsArray = [];
                	optionsArray.push(options.options);
                	options.options = optionsArray;
                }
                
                if (value !== null && typeof value !== 'undefined' &&
                    options.options.includes(value)) {
                    selectMe = value;
                } else {
                    selectMe = options.options[0];
                }
                options.options.forEach(function (option) {
                    optionElement = document.createElement('option');
                    optionElement.text = option;
                    optionElement.value = option;
                    if (option === selectMe) {
                        optionElement.selected = true;
                        optionElement.defaultSelected = true;
                    } else {
                        optionElement.selected = false;
                        optionElement.defaultSelected = false;
                    }

                    valueInput.add(optionElement);
                });
            } else {
                valueInput = document.createElement("input");
                valueInput.setAttribute('type', 'text');
                valueInput.setAttribute('value', value);
            }

            if (role === 'input') {
                // Do not invoke any handlers on input change.  The user must
                // initiate invoctaion with the "react to inputs" button.
                valueInput.setAttribute('class', 'valueInputBox inputRole');
            } else {
                // Invoke setParameter() on change.  Note onchange() also fires when
                // the user deletes a form field value.
                valueInput.setAttribute('onchange', 'setParameter("' + id + '", name, value)');
                valueInput.setAttribute('class', 'valueInputBox parameterRole');
            }

            // Set a unique ID so that this input can be retrieved by the get()
            // function defined in local.js.
            valueInput.setAttribute('id', id + '.' + name);
            valueInput.setAttribute('name', name);

            valueCell.appendChild(valueInput);
        }
        row.appendChild(valueCell);

        // Insert the documentation, if any is found.
        var success = false;
        var docCell;
        if (window.accessorDocs) {
            var docs = window.accessorDocs[id];
            if (docs) {
                // Try with various suffixes.
                var doc = docs[name];
                if (!doc) {
                    doc = docs[name + ' (parameter)'];
                }
                if (!doc) {
                    doc = docs[name + ' (port)'];
                }
                if (!doc) {
                    doc = docs[name + ' (port-parameter)'];
                }
                if (doc) {
                    success = true;
                    docCell = document.createElement("td");

                    docCell.className = 'accessorDocumentation accessorTableData';
                    docCell.innerHTML = doc;
                    row.appendChild(docCell);
                }
            }
        }
        if (!success) {
            docCell = document.createElement("td");

            docCell.setAttribute('class', 'accessorDocumentation accessorWarning');
            docCell.innerHTML = 'No description found';
            row.appendChild(docCell);
        }

        table.appendChild(row);
        return resolve(true);
    });
}

/** Get default documentation from a base accessor or implemented interface.
 *  This ignores description, author, and version fields of the base documentation.
 *  @param docs The data structure to populate with documentation.
 *  @param path The path of the base accessor or interface.
 */
function getBaseDocumentation(docs, path) {
    // Attempt to read the PtDoc file.
    path = normalizePath(path);
    path = path + 'PtDoc.xml';

    var request = new XMLHttpRequest();
    request.overrideMimeType("application/xml");
    // FIXME: Have to do this as a synchronous request with this design
    // because we are populating the data structure that will be used upon
    // returning.
    request.open('GET', path, false); // Pass false for synchronous
    request.send();
    // FIXME: Need to instantiate the base in order to know whether
    // it, in turn, extends or implements anything, and follow that here.
    // Need the id of that here:
    /*
    var implemented = window.accessors[id].implementedInterfaces;
    for (var i = 0; i < implemented.length; i++) {
        getBaseDocumentation(docs, implemented[i]);
    }
    */

    // If the request was successful.
    if (request.status === 200) {
        var properties = request.responseXML.getElementsByTagName('property');
        for (var i = 0; i < properties.length; i++) {
            var property = properties[i];
            var name = property.getAttribute('name');
            if (name !== 'description' &&
                name !== 'author' &&
                name !== 'version') {
                docs[name] = property.getAttribute('value');
            }
        }
    }
}

/** Fetch the top-level index.json file and puts its contents in the specified
 *  docElement.  This function will be invoked recursively to populate
 *  subdirectories.
 * @param baseDirectory The directory to fetch; for example, net for the
 *  net/REST accessor.
 * @param docElement The HTML document element to add content to.
 * @param indent The amount of left indentation, in pixels.
 */

function getIndex(baseDirectory, docElement, indent) {
    var request = new XMLHttpRequest();
    request.overrideMimeType("application/json");
    var path = baseDirectory + 'index.json';
    request.open('GET', path, true); // Pass true for asynchronous
    request.onreadystatechange = function () {
        // If the request is complete (state is 4)
        if (request.readyState === 4) {
            // If the request was successful.
            if (request.status === 200) {
                var response = JSON.parse(request.responseText);
                // Expected response is a list of directories and/or .js files.
                for (var i = 0; i < response.length; i++) {
                    var item = response[i];
                    var content;

                    if (item.indexOf('.js') === item.length - 3) {
                        // Accessor reference.
                        // Strip off the .js

                        content = document.createElement('a');

                        // Remove .accessors/ from baseDirectory.
                        var querystring = "index.html?accessor=" +
                            baseDirectory.substring(11, baseDirectory.length) +
                            item.substring(0, item.length - 3);
                        querystring = querystring.replace('/', '.');
                        content.href = querystring;

                        content.setAttribute('class', 'accessorDirectoryItem');
                        docElement.appendChild(content);
                        content.innerHTML = item.substring(0, item.length - 3);

                    } else if (item.indexOf('.xml') !== -1) {
                        // Obsolete accessor reference.
                        continue;
                    } else {
                        // Directory reference.
                        // FIXME: + and - for expanded and not.
                        content = document.createElement('div');
                        content.setAttribute('class', 'accessorDirectoryItem');
                        content.style.marginLeft = indent + 'px';
                        docElement.appendChild(content);

                        content.innerHTML = item;
                        var id = (baseDirectory + item);
                        // Create an element for the subdirectory.
                        var subElement = document.createElement('div');
                        // Start it hidden.
                        subElement.style.display = 'none';
                        subElement.id = id;
                        docElement.appendChild(subElement);
                        
                        // If 'image' is expanded, start loading cv.js so it
                        // will be available earlier.  It's large.
                        
                        // TODO:  Put in onclick()≥

                        
                        content.onclick = (function (id, indent, getIndex) {
                            return function () {
                                if (id.indexOf('image') > 0) {
                                    var request2 = new XMLHttpRequest();
                                    request2.open('GET', '/accessors/hosts/browser/modules/cv.js', true); // Pass true for asynchronous
                                    request2.send();
                                }
                                
                                toggleVisibility(id, indent, getIndex);
                            };
                        })(id, indent + 10, getIndex);
                    }
                }
            } else {
                var pp = document.createElement('p');
                pp.setAttribute('class', 'accessorError');
                pp.innerHTML = 'No index.json file';
                docElement.appendChild(pp);
            }
        }
    };
    request.send();
}

/** Get data from an input or parameter. This is used by get() and getParameter().
 *  @param name The name of the input (a string).
 *  @param role One of 'input' or 'parameter'.
 *  @param id The id of the accessor.
 *  @return The value received on the input, or null if no value is received.
 */
function getInputOrParameter(name, role, id) {
    var element = document.getElementById(id + '.' + name);
    if (!element) {
        alert('No ' + role + ' named ' + name +
            ' for accessor with id ' + id);
        return null;
    }
    // Depending on the type, we should parse the input or parameter.
    if (!window.accessors || !window.accessors[id]) {
        // The accessors field of the window object has not been set, so
        // just interpret the value as a string.
        return element.value;
    } else {
        var options = window.accessors[id][role + 's'][name];
        if (!options) {
            // This could only occur is somehow the document has an element
            // with the right name, but there is no such input or parameter.
            alert('No record of ' + role + ' named ' + name +
                ' for accessor with id ' + id);
            return null;
        }
        if (!options.type) {
            // The type is unspecified.
            // In this host, we attempt to parse it as JSON, and failing that
            // return a string. Note that we do not want to use eval(), as that
            // could create security risks.
            try {
                return JSON.parse(element.value);
            } catch (err) {
                return element.value;
            }
        } else if (options.type === 'string') {
            return element.value;
        } else {
            // Types JSON, boolean, int, and number should all be parsable
            // as JSON, so we proceed with parsing. This will throw an exception
            // if invalid JSON. Since null is invalid JSON, treat that specially.
            if (!element.value) {
                return null;
            }
            try {
                return JSON.parse(element.value);
            } catch (err) {
                alert('Invalid JSON on ' + role + ' named ' + name + ': ' +
                    element.value + ' for accessor with id ' + id);
                return null;
            }
        }
    }
}

/** Return the text of an accessor definition from the accessor library on the host.
 *  This implementation appends the string '.js' to the specified path
 *  (if it is not already there) and retrieves from the server's accessor
 *  repository the text of the accessor specification.
 *  This is a blocking call.
 *
 *  @param path The path on the server for the JavaScript code, e.g. 'net/REST'.
 */
function getAccessorCode(path) {
    // Strip off a leading '/' if provided.
    while (path.indexOf('/') === 0) {
        path = path.substring(1);
    }
    // The second argument indicates a blocking call, and the third indicates
    // to look in the accessor directory, not in the modules directory.
    return getJavaScript(path, null, false);
}

/** Return the source code of an accessor or module definition.
 *  This implementation appends the string '.js' to the specified path
 *  (if it is not already there) and issues an HTTP GET with the specified path.
 *  If the path begins with '/' or './', then it is used as is.
 *  Otherwise, depending on the third argument, it is prepended with the
 *  location of the directory in which accessors are stored ('/accessors' on this host)
 *  or the directory in which modules are stored ('/accessors/hosts/browser/modules'
 *  on this host).
 *
 *  If no callback function is given, then this is a blocking request.
 *  It will not return until it has the text, and then will return that text.
 *  If a callback is given, then this will issue the HTTP get and return, and
 *  then later invoke the callback when the response has been completely received.
 *  The callback function will be passed two argument: an error string (or null if
 *  no error occurred) and the text of the response (or null if an error occurred).
 *  @param path The path on the server for the JavaScript code.
 *  @param callback The callback function.
 *  @param module True to look in the modules directory for paths that do not
 *   begin with '/' or './'. False (or omitted) to look in '/accessors'.
 */
function getJavaScript(path, callback, module) {
    var index = path.lastIndexOf('.js');
    if (index != path.length - 3) {
        path = path + '.js';
    }
    if (path.indexOf('/') !== 0 && path.indexOf('./') !== 0) {
        // A relative path is provided.
        // Convert this to an absolute path for either a module or an accessor.
        if (module) {
            path = '/accessors/hosts/browser/modules/' + path;
        } else if (path.indexOf('accessors/') !== 0) {
            path = '/accessors/' + path;
        } else {
            path = '/' + path;
        }
    }
    var request = new XMLHttpRequest();
    request.overrideMimeType("application/javascript");
    if (!callback) {
        // Synchronous version.
        request.open('GET', path, false); // Pass false for synchronous
        request.send(); // Send the request now
        // Throw an error if the request was not 200 OK
        if (request.status !== 200) {
            throw 'Failed to get ' + path + ': ' + request.statusText;
        }
        return request.responseText;
    } else {
        // Asynchronous version.
        request.open('GET', path, true); // Pass true for asynchronous
        request.onreadystatechange = function () {
            // If the request is complete (state is 4)
            if (request.readyState === 4) {
                // If the request was successful.
                if (request.status === 200) {
                    callback(null, request.responseText);
                } else {
                    callback('Failed to get ' +
                        path +
                        ': ' + request.statusText,
                        null);
                }
            }
        };
        request.send();
    }
}


/** Return the name of this host.
 *
 *  Return the string "Browser".
 *
 *  @return In browser.js, return "Browser".
 */ 
function getHostName() {
    return "Browser";
};

/** Initialize the specified accessor instance if it has not been initialized and its
 *  exports object has an initialize function.
 *  @param instance The instance.
 */
function initializeIfNecessary(instance) {
    if (!instance.initialized) {
        try {
            instance.initialize();
        } catch (err) {
            alert('Error initializing accessor: ' + err);
            return;
        }
    }
}

/** Normalize the specified accessor path by removing any trailing '.js' and
 *  prepending, if necessary, with '/accessors/'.
 *  @param path The path to normalize.
 */
function normalizePath(path) {
    // Remove any trailing '.js'.
    if (path.indexOf('.js') === path.length - 3) {
        path = path.substring(0, path.length - 3);
    }

    // Make sure the path starts with /accessors so that it will work
    // with the accessor host.
    if (path.indexOf('/') === 0) {
        path = path.substring(1);
    }
    if (path.indexOf('accessors/') !== 0) {
        path = '/accessors/' + path;
    } else {
        path = '/' + path;
    }
    return path;
}

/** Provide an input to the accessor with the specified id.
 *  @param id The id of the accessor.
 *  @param name The name of the input.
 *  @param value The value to provide.
 */
function provideInput(id, name, value) {
    var instance = window.accessors[id];
    initializeIfNecessary(instance);
    instance.provideInput(name, value);
}

/** If the accessor is marked executable, then invoke its react() function.
 *  If it has not been previously initialized, then initialize it first.
 *  Otherwise, provide a message that the accessor is not executable, unless
 *  that message is suppressed by the second argument.
 *  @param id The accessor ID.
 *  @param suppress True to suppress the 'not executable' message.
 */
function reactIfExecutable(id, suppress) {
    if (window.accessors) {
        var instance = window.accessors[id];
        if (instance && instance.executable) {
            initializeIfNecessary(instance);
            try {
                // Call provideInput() on all visible inputs for this accessor.
                // This enables inputHandlers for all inputs even if an input's
                // value has not changed since last execution.
                // Non-visible inputs are not triggered from the UI, but an
                // accessor might send to a non-visible input
                var period;
                var inputs = document.getElementsByClassName('inputRole');
                var element;
                var found, visible;

                for (var i = 0; i < inputs.length; i++) {
                    // Element at 6 parents up has accessor name.
                    // (No ancestor function in plain Javascript.)

                    // Check that this input belongs to the accessor that the
                    // "react to inputs" button was clicked for.  I.e., the
                    // element should have an ancestor with the accessor id.
                    // Also, check if this input is visible.  I.e., does not
                    // have an ancestor with class "invisible".
                    element = inputs[i];
                    found = false;
                    visible = true;

                    while (element.parentNode !== null) {
                        if (element.classList.contains("invisible")) {
                            visible = false;
                        }
                        if (element.parentNode.id === id) {
                            found = true;
                            break;
                        }
                        element = element.parentNode;
                    }

                    if (found && visible) {
                        if (inputs[i].value !== null && inputs[i].value !== "") {
                            // Do not call provideInput for blank fields.
                            // Use "" in a form field to send an empty string as input.
                            provideInput(id, inputs[i].getAttribute('name'),
                                inputs[i].value);
                        }
                    }
                }

                window.accessors[id].react();
            } catch (err) {
                alert('Error executing accessor: ' + err);
            }
            return;
        }
    }
    if (!suppress) {
        alert('Accessor is not executable.');
    }
}

/** Set a parameter of the accessor with the specified id.
 *  @param id The id of the accessor.
 *  @param name The name of the input.
 *  @param value The value to provide.
 */
function setParameter(id, name, value) {
    var instance = window.accessors[id];
    instance.setParameter(name, value);
}

/** Toggle the visibility of an element on the web page.
 *  If the element is empty, then populate it from the directory.
 *  @param id The id of the element.
 *  @param indent The amount by which to indent the contents.
 *  @param getIndex A function that will populate a specified element with an
 *   index of accessors.
 */
function toggleVisibility(id, indent, getIndex) {
    var element = document.getElementById(id);
    if (element) {
        if (element.style.display == 'block') {
            element.style.display = 'none';
        } else {
            if (element.innerHTML === '') {
                // Element is empty. Populate it.
                getIndex(id + '/', element, indent);
            }
            element.style.display = 'block';
        }
    }
}