A (PC8080) VT100 can now connect to a (PCx86) IBM PC AT; only problem seems to be flow control now.

Also, each PCjs debugger can now be independently accessed from an external debugger (eg, "pcx86('ver')" or "pc8080('ver')"); the global '$' symbol is no longer used for debugger access.
TODO: There are still many places where GLOBALVAR should be replaced with PCX86.GLOBALVAR or PC8080.GLOBALVAR as appropriate (which I've finally started doing in each debugger.js file).
This commit is contained in:
Jeff Parsons 2016-08-18 16:46:34 -07:00
commit 5ed8f13840
45 changed files with 10989 additions and 1417 deletions

View file

@ -1561,6 +1561,11 @@ Computer8080.show = function()
computer.printMessage("onShow(" + computer.fInitialized + "," + computer.flags.fPowered + ")");
}
/*
* Note that the FIRST 'onpageshow' event, and therefore the first show() callback, occurs
* AFTER the the initial 'onload' event, and at that point in time, fInitialized will not be set yet.
* So, practically speaking, the first show() callback isn't all that useful.
*/
if (computer.fInitialized && !computer.flags.fPowered) {
/**
* Repower the computer, notifying every component to continue running as-is.

View file

@ -38,6 +38,7 @@ if (DEBUGGER) {
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PC8080 = require("./defines");
var CPUDef8080 = require("./cpudef");
var CPU8080 = require("./cpu");
var Keyboard8080= require("./keyboard");
@ -192,19 +193,19 @@ function Debugger8080(parmsDbg)
* Make it easier to access Debugger8080 commands from an external REPL (eg, the WebStorm
* "live" console window); eg:
*
* $('r')
* $('dw 0:0')
* $('h')
* pc8080('r')
* pc8080('dw 0:0')
* pc8080('h')
* ...
*/
var dbg = this;
if (window) {
if (window['$'] === undefined) {
window['$'] = function(s) { return dbg.doCommands(s); };
if (window[PC8080.APPCLASS] === undefined) {
window[PC8080.APPCLASS] = function(s) { return dbg.doCommands(s); };
}
} else {
if (global['$'] === undefined) {
global['$'] = function(s) { return dbg.doCommands(s); };
if (global[PC8080.APPCLASS] === undefined) {
global[PC8080.APPCLASS] = function(s) { return dbg.doCommands(s); };
}
}
@ -4760,7 +4761,7 @@ if (DEBUGGER) {
}
break;
}
this.println((APPNAME || "PC8080") + " version " + (XMLVERSION || APPVERSION) + " (" + this.cpu.model + (COMPILED? ",RELEASE" : (DEBUG? ",DEBUG" : ",NODEBUG")) + (TYPEDARRAYS? ",TYPEDARRAYS" : (BYTEARRAYS? ",BYTEARRAYS" : ",LONGARRAYS")) + ')');
this.println((PC8080.APPNAME || "PC8080") + " version " + (XMLVERSION || PC8080.APPVERSION) + " (" + this.cpu.model + (PC8080.COMPILED? ",RELEASE" : (PC8080.DEBUG? ",DEBUG" : ",NODEBUG")) + (PC8080.TYPEDARRAYS? ",TYPEDARRAYS" : (PC8080.BYTEARRAYS? ",BYTEARRAYS" : ",LONGARRAYS")) + ')');
this.println(web.getUserAgent());
break;
case '?':

View file

@ -101,7 +101,6 @@ if (NODE) {
global.BYTEARRAYS = BYTEARRAYS;
global.TYPEDARRAYS = TYPEDARRAYS;
global.PC8080 = PC8080;
/*
* TODO: When we're "required" by Node, should we return anything via module.exports?
*/
module.exports = PC8080;
}

View file

@ -116,7 +116,7 @@ function SerialPort8080(parmsSerial) {
}
/*
* No connection until initBus() invokes initConnection().
* No connection until initConnection() is called.
*/
this.sDataReceived = "";
this.connection = this.sendData = null;
@ -396,8 +396,6 @@ SerialPort8080.prototype.initBus = function(cmp, bus, cpu, dbg)
bus.addPortInputTable(this, SerialPort8080.aPortInput, this.portBase);
bus.addPortOutputTable(this, SerialPort8080.aPortOutput, this.portBase);
this.initConnection();
this.setReady();
};
@ -424,14 +422,14 @@ SerialPort8080.prototype.initConnection = function()
var asParts = sConnection.split('->');
if (asParts.length == 2) {
var sSourceID = str.trim(asParts[0]);
if (sSourceID != this.idComponent) return; // this connection string is meant for another instance
if (sSourceID != this.idComponent) return; // this connection string is intended for another instance
var sTargetID = str.trim(asParts[1]);
this.connection = Component.getComponentByID(sTargetID);
if (this.connection) {
var exports = this.connection['exports'];
if (exports) {
this.sendData = exports['receiveData'];
this.printMessage(this.idMachine + '.' + sSourceID + " connected to " + sTargetID, true);
this.status(this.idMachine + '.' + sSourceID + " connected to " + sTargetID);
return;
}
}
@ -451,6 +449,16 @@ SerialPort8080.prototype.initConnection = function()
SerialPort8080.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) {
/*
* We needed to wait until now to make our first inter-machine connection attempt;
* doing this in initBus() was still too early, because initBus() is called in the context
* of onInit() processing for all machines of the same type (eg, PCx86), and if we're
* trying to connect to the port of a machine of a DIFFERENT type (eg, PC8080), it may not
* have been initialized yet.
*/
this.initConnection();
if (!data || !this.restore) {
this.reset();
} else {

View file

@ -38,13 +38,14 @@ if (DEBUGGER) {
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var Interrupts = require("./interrupts");
var Messages = require("./messages");
var Memory = require("./memory");
var Keyboard = require("./keyboard");
var PCX86 = require("./defines");
var CPU = require("./cpu");
var X86 = require("./x86");
var X86Seg = require("./x86seg");
var Interrupts = require("./interrupts");
var Keyboard = require("./keyboard");
var Messages = require("./messages");
var Memory = require("./memory");
}
}
@ -258,19 +259,19 @@ function Debugger(parmsDbg)
* Make it easier to access Debugger commands from an external REPL (eg, the WebStorm
* "live" console window); eg:
*
* $('r')
* $('dw 0:0')
* $('h')
* pcx86('r')
* pcx86('dw 0:0')
* pcx86('h')
* ...
*/
var dbg = this;
if (window) {
if (window['$'] === undefined) {
window['$'] = function(s) { return dbg.doCommands(s); };
if (window[PCX86.APPCLASS] === undefined) {
window[PCX86.APPCLASS] = function(s) { return dbg.doCommands(s); };
}
} else {
if (global['$'] === undefined) {
global['$'] = function(s) { return dbg.doCommands(s); };
if (global[PCX86.APPCLASS] === undefined) {
global[PCX86.APPCLASS] = function(s) { return dbg.doCommands(s); };
}
}
@ -8080,7 +8081,7 @@ if (DEBUGGER) {
this.doClear(asArgs[0]);
break;
case 'd':
if (!COMPILED && sCmd == "debug") {
if (!PCX86.COMPILED && sCmd == "debug") {
window.DEBUG = true;
this.println("DEBUG checks on");
break;
@ -8162,7 +8163,7 @@ if (DEBUGGER) {
}
break;
}
this.println((APPNAME || "PCx86") + " version " + (XMLVERSION || APPVERSION) + " (" + this.cpu.model + (COMPILED? ",RELEASE" : (DEBUG? ",DEBUG" : ",NODEBUG")) + (PREFETCH? ",PREFETCH" : ",NOPREFETCH") + (TYPEDARRAYS? ",TYPEDARRAYS" : (BYTEARRAYS? ",BYTEARRAYS" : ",LONGARRAYS")) + (BACKTRACK? ",BACKTRACK" : ",NOBACKTRACK") + ')');
this.println((PCX86.APPNAME || "PCx86") + " version " + (XMLVERSION || PCX86.APPVERSION) + " (" + this.cpu.model + (PCX86.COMPILED? ",RELEASE" : (PCX86.DEBUG? ",DEBUG" : ",NODEBUG")) + (PCX86.PREFETCH? ",PREFETCH" : ",NOPREFETCH") + (PCX86.TYPEDARRAYS? ",TYPEDARRAYS" : (PCX86.BYTEARRAYS? ",BYTEARRAYS" : ",LONGARRAYS")) + (PCX86.BACKTRACK? ",BACKTRACK" : ",NOBACKTRACK") + ')');
this.println(web.getUserAgent());
break;
case 'x':
@ -8176,7 +8177,7 @@ if (DEBUGGER) {
this.doHelp();
break;
case 'n':
if (!COMPILED && sCmd == "nodebug") {
if (!PCX86.COMPILED && sCmd == "nodebug") {
window.DEBUG = false;
this.println("DEBUG checks off");
break;

View file

@ -188,7 +188,6 @@ if (NODE) {
global.DESKPRO386 = DESKPRO386;
global.PAGEBLOCKS = PAGEBLOCKS;
global.PCX86 = PCX86;
/*
* TODO: When we're "required" by Node, should we return anything via module.exports?
*/
module.exports = PCX86;
}

View file

@ -32,6 +32,7 @@
"use strict";
if (NODE) {
var str = require("../../shared/lib/strlib");
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
@ -93,7 +94,7 @@ function ParallelPort(parmsParallel) {
this.consoleOutput = null;
/**
* controlIOBuffer is a DOM element, if any, bound to the port (currently used for output only; see echoByte()).
* controlIOBuffer is a DOM element bound to the port (currently used for output only; see transmitByte()).
*
* @type {Object}
*/
@ -117,7 +118,7 @@ function ParallelPort(parmsParallel) {
* property {number} iAdapter
* property {number} portBase
* property {number} nIRQ
* property {Object} controlIOBuffer is a DOM element, if any, bound to the port (for rudimentary output; see echoByte())
* property {Object} controlIOBuffer is a DOM element bound to the port (for rudimentary output; see transmitByte())
*
* NOTE: This class declaration started as a way of informing the code inspector of the controlIOBuffer property,
* which remained undefined until a setBinding() call set it later, but I've since decided that explicitly
@ -406,7 +407,7 @@ ParallelPort.prototype.outData = function(port, bOut, addrFrom)
this.printMessageIO(port, bOut, addrFrom, "DATA");
this.bData = bOut;
this.bStatus |= ParallelPort.STATUS.NOTREADY;
if (this.echoByte(bOut)) {
if (this.transmitByte(bOut)) {
this.bStatus &= ~ParallelPort.STATUS.NOTREADY;
}
this.updateIRR();
@ -444,14 +445,18 @@ ParallelPort.prototype.updateIRR = function()
};
/**
* echoByte(b)
* transmitByte(b)
*
* @this {ParallelPort}
* @param {number} b
* @return {boolean} true if echoed, false if not
* @return {boolean} true if transmitted, false if not
*/
ParallelPort.prototype.echoByte = function(b)
ParallelPort.prototype.transmitByte = function(b)
{
var fTransmitted = false;
this.printMessage("transmitByte(" + str.toHexByte(b) + ")");
if (this.controlIOBuffer) {
if (b == 0x08) {
this.controlIOBuffer.value = this.controlIOBuffer.value.slice(0, -1);
@ -460,7 +465,7 @@ ParallelPort.prototype.echoByte = function(b)
this.controlIOBuffer.value += String.fromCharCode(b);
this.controlIOBuffer.scrollTop = this.controlIOBuffer.scrollHeight;
}
return true;
fTransmitted = true;
}
if (this.consoleOutput != null) {
if (b == 0x0A || this.consoleOutput.length >= 1024) {
@ -470,9 +475,10 @@ ParallelPort.prototype.echoByte = function(b)
if (b != 0x0A) {
this.consoleOutput += String.fromCharCode(b);
}
return true;
fTransmitted = true;
}
return false;
return fTransmitted;
};
/*

View file

@ -97,7 +97,16 @@ function SerialPort(parmsSerial) {
this.consoleOutput = null;
/**
* controlIOBuffer is a DOM element, if any, bound to the port (currently used for output only; see echoByte()).
* controlIOBuffer is a DOM element bound to the port (currently used for output only; see transmitByte()).
*
* Example: CTTY COM2
*
* The CTTY DOS command redirects all CON I/O to the specified serial port (eg, COM2), which it assumes is
* connected to a serial terminal, and therefore anything it *transmits* via COM2 will be displayed by the
* terminal. It further assumes that anything typed on such a terminal is NOT displayed, so as DOS *receives*
* serial input, DOS *transmits* the appropriate characters back to the terminal via COM2.
*
* As a result, controlIOBuffer only needs to be updated by the transmitByte() function.
*
* @type {Object}
*/
@ -105,7 +114,7 @@ function SerialPort(parmsSerial) {
/*
* If controlIOBuffer is being used AND 'tabSize' is set, then we make an attempt to monitor the characters
* being echoed via echoByte(), maintain a logical column position, and convert any tabs into the appropriate
* being echoed via transmitByte(), maintain a logical column position, and convert any tabs into the appropriate
* number of spaces.
*
* charBOL, if nonzero, is a character to automatically output at the beginning of every line. This probably
@ -128,7 +137,7 @@ function SerialPort(parmsSerial) {
}
/*
* No connection until initBus() invokes initConnection().
* No connection until initConnection() is called.
*/
this.sDataReceived = "";
this.connection = this.sendData = null;
@ -146,7 +155,7 @@ function SerialPort(parmsSerial) {
* property {number} iAdapter
* property {number} portBase
* property {number} nIRQ
* property {Object} controlIOBuffer is a DOM element, if any, bound to the port (for rudimentary output; see echoByte())
* property {Object} controlIOBuffer is a DOM element bound to the port (for rudimentary output; see transmitByte())
*
* NOTE: This class declaration started as a way of informing the code inspector of the controlIOBuffer property,
* which remained undefined until a setBinding() call set it later, but I've since decided that explicitly
@ -459,8 +468,6 @@ SerialPort.prototype.initBus = function(cmp, bus, cpu, dbg)
bus.addPortInputTable(this, SerialPort.aPortInput, this.portBase);
bus.addPortOutputTable(this, SerialPort.aPortOutput, this.portBase);
this.initConnection();
this.setReady();
};
@ -487,14 +494,14 @@ SerialPort.prototype.initConnection = function()
var asParts = sConnection.split('->');
if (asParts.length == 2) {
var sSourceID = str.trim(asParts[0]);
if (sSourceID != this.idComponent) return; // this connection string is meant for another instance
if (sSourceID != this.idComponent) return; // this connection string is intended for another instance
var sTargetID = str.trim(asParts[1]);
this.connection = Component.getComponentByID(sTargetID);
if (this.connection) {
var exports = this.connection['exports'];
if (exports) {
this.sendData = exports['receiveData'];
this.printMessage(this.idMachine + '.' + sSourceID + " connected to " + sTargetID, true);
this.status(this.idMachine + '.' + sSourceID + " connected to " + sTargetID);
return;
}
}
@ -514,6 +521,16 @@ SerialPort.prototype.initConnection = function()
SerialPort.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) {
/*
* We needed to wait until now to make our first inter-machine connection attempt;
* doing this in initBus() was still too early, because initBus() is called in the context
* of onInit() processing for all machines of the same type (eg, PCx86), and if we're
* trying to connect to the port of a machine of a DIFFERENT type (eg, PC8080), it may not
* have been initialized yet.
*/
this.initConnection();
if (!data || !this.restore) {
this.reset();
} else {
@ -803,7 +820,7 @@ SerialPort.prototype.outTHR = function(port, bOut, addrFrom)
} else {
this.bTHR = bOut;
this.bLSR &= ~(SerialPort.LSR.THRE | SerialPort.LSR.TSRE);
if (this.echoByte(bOut)) {
if (this.transmitByte(bOut)) {
this.bLSR |= (SerialPort.LSR.THRE | SerialPort.LSR.TSRE);
/*
* QUESTION: Does this mean we should also flush/zero bTHR?
@ -888,14 +905,24 @@ SerialPort.prototype.updateIRR = function()
};
/**
* echoByte(b)
* transmitByte(b)
*
* @this {SerialPort}
* @param {number} b
* @return {boolean} true if echoed, false if not
* @return {boolean} true if transmitted, false if not
*/
SerialPort.prototype.echoByte = function(b)
SerialPort.prototype.transmitByte = function(b)
{
var fTransmitted = false;
this.printMessage("transmitByte(" + str.toHexByte(b) + ")");
if (this.sendData) {
if (this.sendData.call(this.connection, b)) {
fTransmitted = true;
}
}
if (this.controlIOBuffer) {
if (b == 0x0D) {
this.iLogicalCol = 0;
@ -908,8 +935,9 @@ SerialPort.prototype.echoByte = function(b)
if (this.iLogicalCol > 0) this.iLogicalCol--;
}
else {
var s = String.fromCharCode(b);
var nChars = (b >= 0x20? 1 : 0);
var s = str.toASCIICode(b); // formerly: String.fromCharCode(b);
var nChars = s.length; // formerly: (b >= 0x20? 1 : 0);
if (b < 0x20 && nChars == 1) nChars = 0;
if (b == 0x09) {
var tabSize = this.tabSize || 8;
nChars = tabSize - (this.iLogicalCol % tabSize);
@ -920,9 +948,9 @@ SerialPort.prototype.echoByte = function(b)
this.controlIOBuffer.scrollTop = this.controlIOBuffer.scrollHeight;
this.iLogicalCol += nChars;
}
return true;
fTransmitted = true;
}
if (this.consoleOutput != null) {
else if (this.consoleOutput != null) {
if (b == 0x0A || this.consoleOutput.length >= 1024) {
this.println(this.consoleOutput);
this.consoleOutput = "";
@ -930,9 +958,10 @@ SerialPort.prototype.echoByte = function(b)
if (b != 0x0A) {
this.consoleOutput += String.fromCharCode(b);
}
return true;
fTransmitted = true;
}
return false;
return fTransmitted;
};
/*

View file

@ -403,34 +403,37 @@ str.trim = function(s)
return s.replace(/^\s+|\s+$/g, "");
};
/*
* Any codes commented out in the following table are deemed "printable"
*/
str.aASCIICodes = {
0x00: "NUL",
0x01: "SOH", // Start of Heading
0x02: "STX", // Start of Text
0x03: "ETX", // End of Text
0x04: "EOT", // End of Transmission
0x05: "ENQ", // Enquiry
0x06: "ACK", // Acknowledge
0x07: "BEL", // Bell
0x08: "BS", // Backspace
0x09: "TAB", // Horizontal Tab
0x0A: "LF", // Line Feed (New Line)
0x0B: "VT", // Vertical Tab
0x0C: "FF", // Form Feed (New Page)
0x0D: "CR", // Carriage Return
0x0E: "SO", // Shift Out
0x0F: "SI", // Shift In
0x10: "DLE", // Data Link Escape
0x11: "DC1", // Device Control 1
0x12: "DC2", // Device Control 2
0x13: "DC3", // Device Control 3
0x14: "DC4", // Device Control 4
0x15: "NAK", // Negative Acknowledge
0x16: "SYN", // Synchronous Idle
0x17: "ETB", // End of Transmission Block
0x18: "CAN", // Cancel
0x19: "EM", // End of Medium
0x1A: "SUB", // Substitute
0x01: "SOH", // (CTRL_A) Start of Heading
0x02: "STX", // (CTRL_B) Start of Text
0x03: "ETX", // (CTRL_C) End of Text
0x04: "EOT", // (CTRL_D) End of Transmission
0x05: "ENQ", // (CTRL_E) Enquiry
0x06: "ACK", // (CTRL_F) Acknowledge
0x07: "BEL", // (CTRL_G) Bell
0x08: "BS", // (CTRL_H) Backspace
0x09: "TAB", // (CTRL_I) Horizontal Tab
// 0x0A: "LF", // (CTRL_J) Line Feed (New Line)
0x0B: "VT", // (CTRL_K) Vertical Tab
0x0C: "FF", // (CTRL_L) Form Feed (New Page)
0x0D: "CR", // (CTRL_M) Carriage Return
0x0E: "SO", // (CTRL_N) Shift Out
0x0F: "SI", // (CTRL_O) Shift In
0x10: "DLE", // (CTRL_P) Data Link Escape
0x11: "XON", // (CTRL_Q) Device Control 1 (aka DC1)
0x12: "DC2", // (CTRL_R) Device Control 2
0x13: "XOFF", // (CTRL_S) Device Control 3 (aka DC3)
0x14: "DC4", // (CTRL_T) Device Control 4
0x15: "NAK", // (CTRL_U) Negative Acknowledge
0x16: "SYN", // (CTRL_V) Synchronous Idle
0x17: "ETB", // (CTRL_W) End of Transmission Block
0x18: "CAN", // (CTRL_X) Cancel
0x19: "EM", // (CTRL_Y) End of Medium
0x1A: "SUB", // (CTRL_Z) Substitute
0x1B: "ESC", // Escape
0x1C: "FS", // File Separator
0x1D: "GS", // Group Separator

View file

@ -735,25 +735,34 @@ web.onClickRepeat = function(e, msDelay, msRepeat, fn)
};
web.aPageEventHandlers = {
'init': [], // list of window 'onload' handlers
'show': [], // list of window 'onpageshow' handlers
'exit': [] // list of window 'onunload' handlers (although we prefer to use 'onbeforeunload' if possible)
'init': [], // list of window 'onload' handlers
'show': [], // list of window 'onpageshow' handlers
'exit': [] // list of window 'onunload' handlers (although we prefer to use 'onbeforeunload' if possible)
};
web.fPageReady = false; // set once the browser's first page initialization has occurred
web.fPageEventsEnabled = true;
web.fPageLoaded = false; // set once the page's first 'onload' event has occurred
web.fPageShowed = false; // set once the page's first 'onpageshow' event has occurred
web.fPageEventsEnabled = true; // default is true, set to false (or true) by enablePageEvents()
/**
* onPageEvent(sName, fn)
*
* For 'onload', 'onunload', and 'onpageshow' events, most callers should NOT use this function, but
* instead use web.onInit(), web.onShow(), and web.onExit(), respectively.
*
* The only components that should still use onPageEvent() are THIS component (see the bottom of this file)
* and components that need to capture other events (eg, the 'onresize' event in the Video component).
*
* This function creates a chain of callbacks, allowing multiple JavaScript modules to define handlers
* for the same event, which wouldn't be possible if everyone modified window['onload'], window['onunload'],
* etc, themselves. However, that's less of a concern now, because assuming everyone else is now using
* onInit(), onExit(), etc, then there really IS only one component setting the window callback: this one.
*
* NOTE: It's risky to refer to obscure event handlers with "dot" names, because the Closure Compiler may
* erroneously replace them (eg, window.onpageshow is a good example).
*
* @param {string} sFunc
* @param {function()} fn
*
* Use this instead of setting window['onload'], window['onunload'], etc.
* Allows multiple JavaScript modules to define a handler for the same event.
*
* Moreover, it's risky to refer to obscure event handlers with "dot" names, because
* the Closure Compiler may erroneously replace them (eg, window.onpageshow is a good example).
*/
web.onPageEvent = function(sFunc, fn)
{
@ -777,9 +786,9 @@ web.onPageEvent = function(sFunc, fn)
/**
* onInit(fn)
*
* @param {function()} fn
*
* Use this instead of setting window.onload. Allows multiple JavaScript modules to define their own 'onload' event handler.
*
* @param {function()} fn
*/
web.onInit = function(fn)
{
@ -837,7 +846,8 @@ web.enablePageEvents = function(fEnable)
{
if (!web.fPageEventsEnabled && fEnable) {
web.fPageEventsEnabled = true;
if (web.fPageReady) web.sendPageEvent('init');
if (web.fPageLoaded) web.sendPageEvent('init');
if (web.fPageShowed) web.sendPageEvent('show');
return;
}
web.fPageEventsEnabled = fEnable;
@ -857,8 +867,18 @@ web.sendPageEvent = function(sEvent)
}
};
web.onPageEvent('onload', function onPageLoad() { web.fPageReady = true; web.doPageEvent(web.aPageEventHandlers['init']); });
web.onPageEvent('onpageshow', function onPageShow() { web.doPageEvent(web.aPageEventHandlers['show']); });
web.onPageEvent(web.isUserAgent("Opera") || web.isUserAgent("iOS")? 'onunload' : 'onbeforeunload', function onPageUnload() { web.doPageEvent(web.aPageEventHandlers['exit']); });
web.onPageEvent('onload', function onPageLoad() {
web.fPageLoaded = true;
web.doPageEvent(web.aPageEventHandlers['init']);
});
web.onPageEvent('onpageshow', function onPageShow() {
web.fPageShowed = true;
web.doPageEvent(web.aPageEventHandlers['show']);
});
web.onPageEvent(web.isUserAgent("Opera") || web.isUserAgent("iOS")? 'onunload' : 'onbeforeunload', function onPageUnload() {
web.doPageEvent(web.aPageEventHandlers['exit']);
});
if (NODE) module.exports = web;