diff --git a/blog/2015/01/17/README.md b/blog/2015/01/17/README.md index ce4cbc1a7..c8a1f1f38 100644 --- a/blog/2015/01/17/README.md +++ b/blog/2015/01/17/README.md @@ -1,8 +1,8 @@ PCjs Uncompiled --- Most machines on [pcjs.org](http://www.pcjs.org/) run with a compiled version of PCjs, which is produced -by running PCjs JavaScript source code through Google's Closure Compiler, yielding a smaller (minified) version -that loads and runs much faster than the original source code. +by running PCjs JavaScript source code through Google's Closure Compiler, yielding a smaller (minified) +version that loads and runs much faster than the original source code. However, certain features are disabled in the compiled versions, including a new BACKTRACK feature that makes it possible to track the contents of memory locations and registers back to their source (eg, to a ROM @@ -17,7 +17,7 @@ Here's how you embed an "uncompiled" machine in a PCjs Markdown file: [Embedded IBM PC](/devices/pc/machine/5170/ega/1152kb/rev3/ "PCjs:at-ega-1152k-rev3::uncompiled:debugger") -And here's that example in action, using the current Markdown (README.md) file: +And here's that example in action, in the Markdown ([README.md]()) file you are reading right now: [Embedded IBM PC](/devices/pc/machine/5170/ega/1152kb/rev3/ "PCjs:at-ega-1152k-rev3::uncompiled:debugger") diff --git a/devices/pc/panel/livestats.xml b/devices/pc/panel/btpanel.xml similarity index 76% rename from devices/pc/panel/livestats.xml rename to devices/pc/panel/btpanel.xml index 6fd9ef77f..fc3106bfc 100644 --- a/devices/pc/panel/livestats.xml +++ b/devices/pc/panel/btpanel.xml @@ -2,7 +2,7 @@ Control Panel - + @@ -13,8 +13,9 @@ Run Step Reset - Fast Save + Speed + Stopped diff --git a/docs/pcjs/demos/components.css b/docs/pcjs/demos/components.css index 1a84597c3..80b7b1a8f 100644 --- a/docs/pcjs/demos/components.css +++ b/docs/pcjs/demos/components.css @@ -104,6 +104,22 @@ line-height: 19px; background-color: #000000; } +.pcjs-video-object { + clear: both; + height: auto; + position: relative; +} +.pcjs-video-object textarea { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + opacity: 0; + border: 0; + padding: 0; + line-height: 0; +} .pcjs-reference { float: left; font-size: x-small; diff --git a/docs/pcjs/demos/components.xsl b/docs/pcjs/demos/components.xsl index 7b2a671e7..8b17c4e1e 100644 --- a/docs/pcjs/demos/components.xsl +++ b/docs/pcjs/demos/components.xsl @@ -349,6 +349,9 @@ + + + diff --git a/modules/pcjs/lib/bus.js b/modules/pcjs/lib/bus.js index 7e92107b4..a50b2648a 100644 --- a/modules/pcjs/lib/bus.js +++ b/modules/pcjs/lib/bus.js @@ -139,7 +139,7 @@ function Bus(parmsBus, cpu, dbg) * [1]: registered function to call for every I/O access * * The registered function is called with the port address, and if the access was triggered by the CPU, - * the physical address (EIP) that the access occurred from. + * the linear address (LIP) that the access occurred from. * * WARNING: Unlike the (old) read and write memory notification functions, these support only one * pair of input/output functions per port. A more sophisticated architecture could support a list @@ -291,7 +291,7 @@ Bus.prototype.powerUp = function(data, fRepower) }; /** - * addMemory(addr, size, fReadOnly, controller) + * addMemory(addr, size, type, controller) * * Adds new Memory blocks to the specified address range. Any Memory blocks previously * added to that range must first be removed via removeMemory(); otherwise, you'll get @@ -303,11 +303,11 @@ Bus.prototype.powerUp = function(data, fRepower) * @this {Bus} * @param {number} addr is the starting physical address of the memory address range * @param {number} size of the length in bytes of the range; must be a multiple of BLOCK_SIZE - * @param {boolean} [fReadOnly] is true if the memory must be read-only; default is read-write + * @param {number} type is one of the Memory.TYPE constants * @param {Object} [controller] is an optional memory controller component * @return {boolean} true if successful, false if not */ -Bus.prototype.addMemory = function(addr, size, fReadOnly, controller) +Bus.prototype.addMemory = function(addr, size, type, controller) { if (!(addr & this.blockLimit) && size && !(size & this.blockLimit)) { var iBlock = addr >> this.blockShift; @@ -317,7 +317,7 @@ Bus.prototype.addMemory = function(addr, size, fReadOnly, controller) return this.reportError(1, addr, size); } addr = iBlock * this.blockSize; - block = this.aMemBlocks[iBlock++] = new Memory(addr, this.blockSize, fReadOnly, controller); + block = this.aMemBlocks[iBlock++] = new Memory(addr, this.blockSize, type, controller); if (DEBUGGER) block.setDebugInfo(this.cpu, this.dbg, addr, this.blockSize); size -= this.blockSize; } @@ -912,7 +912,7 @@ Bus.prototype.saveMemory = function() * This restores the contents of all Memory blocks; called by X86CPU.restore(). * * In theory, we ONLY have to save/restore block contents. Other block attributes, - * like fReadOnly, the memory controller (if any), and the active memory access functions, + * like the type, the memory controller (if any), and the active memory access functions, * should already be restored, since every component (re)allocates all the memory blocks * it was using when it's restored. And since the CPU is guaranteed to be the last * component to be restored, all those blocks (and their attributes) should be in place now. @@ -948,13 +948,13 @@ Bus.prototype.restoreMemory = function(a) }; /** - * addMemoryBreakpoint(addr, fWrite) + * addMemBreak(addr, fWrite) * * @this {Bus} * @param {number} addr * @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint */ -Bus.prototype.addMemoryBreakpoint = function(addr, fWrite) +Bus.prototype.addMemBreak = function(addr, fWrite) { if (DEBUGGER) { var iBlock = addr >> this.blockShift; @@ -963,13 +963,13 @@ Bus.prototype.addMemoryBreakpoint = function(addr, fWrite) }; /** - * removeMemoryBreakpoint(addr, fWrite) + * removeMemBreak(addr, fWrite) * * @this {Bus} * @param {number} addr * @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint */ -Bus.prototype.removeMemoryBreakpoint = function(addr, fWrite) +Bus.prototype.removeMemBreak = function(addr, fWrite) { if (DEBUGGER) { var iBlock = addr >> this.blockShift; @@ -1006,7 +1006,7 @@ Bus.prototype.addPortInputBreak = function(port) * @param {number} start port address * @param {number} end port address * @param {Component} component - * @param {function(number,number)} fn is called with the port and EIP values at the time of the input + * @param {function(number,number)} fn is called with the port and LIP values at the time of the input */ Bus.prototype.addPortInputNotify = function(start, end, component, fn) { @@ -1049,7 +1049,7 @@ Bus.prototype.addPortInputTable = function(component, table, offset) * * @this {Bus} * @param {number} port - * @param {number} [addrFrom] is the EIP value at the time of the input + * @param {number} [addrFrom] is the LIP value at the time of the input * @return {number} simulated port value (0xff if none) * * NOTE: It seems that at least parts of the ROM BIOS (like the RS-232 probes around F000:E5D7 in the 5150 BIOS) @@ -1130,7 +1130,7 @@ Bus.prototype.addPortOutputBreak = function(port) * @param {number} start port address * @param {number} end port address * @param {Component} component - * @param {function(number,number)} fn is called with the port and EIP values at the time of the output + * @param {function(number,number)} fn is called with the port and LIP values at the time of the output */ Bus.prototype.addPortOutputNotify = function(start, end, component, fn) { @@ -1174,7 +1174,7 @@ Bus.prototype.addPortOutputTable = function(component, table, offset) * @this {Bus} * @param {number} port * @param {number} bOut - * @param {number} [addrFrom] is the EIP value at the time of the output + * @param {number} [addrFrom] is the LIP value at the time of the output */ Bus.prototype.checkPortOutputNotify = function(port, bOut, addrFrom) { diff --git a/modules/pcjs/lib/cpu.js b/modules/pcjs/lib/cpu.js index 051c5a41a..2401ab2be 100644 --- a/modules/pcjs/lib/cpu.js +++ b/modules/pcjs/lib/cpu.js @@ -177,17 +177,9 @@ CPU.prototype.initBus = function(cmp, bus, cpu, dbg) this.cmp = cmp; /* * Attach the Video component to the CPU, so that the CPU can periodically update - * the video display via displayVideo(), as cycles permit. + * the video display via updateVideo(), as cycles permit. */ - var video = cmp.getComponentByType("Video"); - if (video) { - this.displayVideo = function onDisplayVideo() { - video.updateScreen(); - }; - this.setFocus = function onSetFocus() { - video.setFocus(); - }; - } + this.video = cmp.getComponentByType("Video"); /* * Attach the ChipSet component to the CPU, so that it can obtain the IDT vector number of * pending hardware interrupts, in response to ChipSet's updateINTR() notifications. @@ -453,37 +445,41 @@ CPU.prototype.displayReg = function(sReg, nVal, cch) }; /** - * displayStatus() + * updateStatus(fForce) * - * This is implemented by the X86CPU component, which subclasses us. + * This provides periodic Control Panel updates (eg, a few times per second; see STATUS_UPDATES_PER_SECOND). + * The X86CPU subclasses updateStatus() to take care of any DOM updates (eg, register values) while the CPU is running. * * @this {CPU} * @param {boolean} [fForce] */ -CPU.prototype.displayStatus = function(fForce) +CPU.prototype.updateStatus = function(fForce) { + if (this.cmp && this.cmp.panel) this.cmp.panel.updateStatus(); }; /** - * displayVideo() + * updateVideo() * - * This is implemented by the Video component (installed when initBus() is called). + * Any high-frequency updates should be performed here. Avoid DOM updates, since updateVideo() can be called up to + * 60 times per second (see VIDEO_UPDATES_PER_SECOND). * * @this {CPU} */ -CPU.prototype.displayVideo = function() +CPU.prototype.updateVideo = function() { + if (this.video) this.video.updateScreen(); + if (this.cmp && this.cmp.panel) this.cmp.panel.updateAnimation(); }; /** * setFocus() * - * This is implemented by the Video component (installed when initBus() is called). - * * @this {CPU} */ CPU.prototype.setFocus = function() { + if (this.video) this.video.setFocus(); }; /** @@ -605,8 +601,8 @@ CPU.prototype.addCycles = function(nCycles, fEndStep) * to the time we expected the virtual hardware to take (eg, 1000ms/50 or 20ms), and if we still have time * remaining, we sleep the remaining time (or 0ms if there's no remaining time), and then restart runCPU(). * - * Similarly, whenever the "next video update" cycle counter goes to (or below) zero, we call displayVideo(), - * and whenever the "next status update" cycle counter goes to (or below) zero, we call displayStatus(). + * Similarly, whenever the "next video update" cycle counter goes to (or below) zero, we call updateVideo(), + * and whenever the "next status update" cycle counter goes to (or below) zero, we call updateStatus(). * * @this {CPU} * @param {boolean} [fRecalc] is true if the caller wants to recalculate thresholds based on the most recent @@ -996,13 +992,13 @@ CPU.prototype.runCPU = function(fOnClick) this.aCounts.nCyclesNextVideoUpdate -= nCycles; if (this.aCounts.nCyclesNextVideoUpdate <= 0) { this.aCounts.nCyclesNextVideoUpdate += this.aCounts.nCyclesPerVideoUpdate; - this.displayVideo(); + this.updateVideo(); } this.aCounts.nCyclesNextStatusUpdate -= nCycles; if (this.aCounts.nCyclesNextStatusUpdate <= 0) { this.aCounts.nCyclesNextStatusUpdate += this.aCounts.nCyclesPerStatusUpdate; - this.displayStatus(); + this.updateStatus(); } this.aCounts.nCyclesNextYield -= nCycles; @@ -1046,7 +1042,7 @@ CPU.prototype.startCPU = function(fSetFocus) if (this.chipset) this.chipset.setSpeaker(); var controlRun = this.bindings["run"]; if (controlRun) controlRun.textContent = "Halt"; - this.displayStatus(true); + this.updateStatus(true); if (fSetFocus) this.setFocus(); } }; @@ -1104,8 +1100,8 @@ CPU.prototype.stopCPU = function(fComplete) */ CPU.prototype.updateCPU = function() { - this.displayVideo(); - this.displayStatus(); + this.updateVideo(); + this.updateStatus(); }; /** diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index f9c53267b..ec5911669 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -1623,7 +1623,7 @@ if (DEBUGGER) { * * @this {Debugger} * @param {number} nInt - * @param {number} addr (EIP after the "INT n" instruction has been fetched but not dispatched) + * @param {number} addr (LIP after the "INT n" instruction has been fetched but not dispatched) * @return {boolean} true if message generated (which in turn triggers addIntReturn() inside checkIntNotify()), false if not */ Debugger.prototype.messageInt = function(nInt, addr) @@ -2465,13 +2465,13 @@ if (DEBUGGER) { this.aBreakExec = ["exec"]; if (this.aBreakRead !== undefined) { for (i = 1; i < this.aBreakRead.length; i++) { - this.bus.removeMemoryBreakpoint(this.getAddr(this.aBreakRead[i]), false); + this.bus.removeMemBreak(this.getAddr(this.aBreakRead[i]), false); } } this.aBreakRead = ["read"]; if (this.aBreakWrite !== undefined) { for (i = 1; i < this.aBreakWrite.length; i++) { - this.bus.removeMemoryBreakpoint(this.getAddr(this.aBreakWrite[i]), true); + this.bus.removeMemBreak(this.getAddr(this.aBreakWrite[i]), true); } } this.aBreakWrite = ["write"]; @@ -2507,7 +2507,7 @@ if (DEBUGGER) { aAddr[3] = fTemp; aBreak.push(aAddr); if (aBreak != this.aBreakExec) { - this.bus.addMemoryBreakpoint(this.getAddr(aAddr), aBreak == this.aBreakWrite); + this.bus.addMemBreak(this.getAddr(aAddr), aBreak == this.aBreakWrite); } if (!fTemp) this.println("breakpoint enabled: " + this.hexAddr(aAddr) + " (" + aBreak[0] + ")"); this.historyInit(); @@ -2536,7 +2536,7 @@ if (DEBUGGER) { if (fRemove) { aBreak.splice(i, 1); if (aBreak != this.aBreakExec) { - this.bus.removeMemoryBreakpoint(addr, aBreak == this.aBreakWrite); + this.bus.removeMemBreak(addr, aBreak == this.aBreakWrite); } if (!aAddrBreak[3]) this.println("breakpoint cleared: " + this.hexAddr(aAddrBreak) + " (" + aBreak[0] + ")"); this.historyInit(); @@ -2592,7 +2592,7 @@ if (DEBUGGER) { for (var i = 1; i < aBreak.length; i++) { var addrBreak = this.getAddr(aBreak[i]); if (addrBreak >= addr && addrBreak < addr + size) { - this.bus.addMemoryBreakpoint(addrBreak, aBreak == this.aBreakWrite); + this.bus.addMemBreak(addrBreak, aBreak == this.aBreakWrite); } } }; diff --git a/modules/pcjs/lib/defines.js b/modules/pcjs/lib/defines.js index 319d46e0c..b22575df4 100644 --- a/modules/pcjs/lib/defines.js +++ b/modules/pcjs/lib/defines.js @@ -98,7 +98,7 @@ var FATARRAYS = false; * TYPEDARRAYS enables use of typed arrays for Memory blocks. This used to be a compile-time-only option, but I've * added Memory access functions for typed arrays (see Memory.afnTypedArray), so support can be enabled dynamically. * - * However, TYPEDARRAYS has always been slightly slower than the original NUMARRAYS implementation (which uses an + * However, TYPEDARRAYS has always been slightly slower than the original DWORDARRAYS implementation (which uses an * Array of numbers that stores 32 bits -- 4 consecutive bytes -- per number), so TYPEDARRAYS is completely disabled. * * See the Memory component for details. @@ -117,8 +117,8 @@ var BACKTRACK = true; * @define {boolean} * * Enables instruction sampling (a work-in-progress). This was used briefly as an internal debugging aid, to - * periodically record EIP values in a fixed-length sampling buffer, halting execution once the sampling buffer - * was full, and then compare those sampled EIP values to corresponding EIP values on subsequent runs, to look + * periodically record LIP values in a fixed-length sampling buffer, halting execution once the sampling buffer + * was full, and then compare those sampled LIP values to corresponding LIP values on subsequent runs, to look * for deviations. In theory, every run is supposed to be absolutely identical, even if you interrupt execution * with the Debugger or enable/disable different sets of messages, but in practice, that's hard to guarantee. */ diff --git a/modules/pcjs/lib/disk.js b/modules/pcjs/lib/disk.js index 5dd5a85fc..5481ed018 100644 --- a/modules/pcjs/lib/disk.js +++ b/modules/pcjs/lib/disk.js @@ -889,8 +889,8 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath) * "logical" sector numbers for volume-relative block addresses (aka LBAs or Logical Block Addresses), and * 0-based "physical" sector numbers for disk-relative block addresses (aka PBAs or Physical Block Addresses). * - * Our use of the term LBA differs from the popular usage of the term, in which disk controllers use LBA - * numbers instead of CHS values. In our world, those controllers would actually be using PBA numbers. + * Also, our use of the term LBA differs from that of more modern disk controllers; in the pre-modern world + * of PCjs, what we call PBA numbers are what those controllers would call LBA numbers. * * @this {Disk} */ @@ -905,6 +905,19 @@ Disk.prototype.buildFileTable = function() var cbDisk = this.nCylinders * this.nHeads * this.nSectors * this.cbSector; + /* + * At this point, if this is a remote disk, you may see some warning messages in your browser's console, + * like this message from Chrome: + * + * "Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects + * to the end user's experience. For more help, check http://xhr.spec.whatwg.org/." + * + * This is because I was lazy and made the buildFileTable() worker function getSector() use the synchronous + * form of seek(). For development purposes, that was fine, but... TODO: Eventually change buildFileTable() + * to use async I/O. + */ + if (this.fRemote) this.log("ignore synchronous XMLHttpRequest warnings here for now...."); + var sectorBoot = this.getSector(0); if (!sectorBoot) { if (DEBUG && this.messageEnabled()) { @@ -1279,9 +1292,9 @@ Disk.prototype.updateSector = function(file, pba, off) /** * getSectorData(sector, off, len) * - * NOTE: Yes, this function is not the most efficient way to read a byte/word/dword value from within - * a sector, but given the different states a sector may be in, it's certainly the simplest and safest, - * and it's not clear that we need to be superfast anyway. + * NOTE: Yes, this function is not the most efficient way to read a byte/word/dword value from within a sector, + * but given the different states a sector may be in, it's certainly the simplest and safest, and since this is + * only used by buildFileTable() and its progeny, it's not clear that we need to be superfast anyway. * * @this {Disk} * @param {Object} sector diff --git a/modules/pcjs/lib/memory.js b/modules/pcjs/lib/memory.js index b9b4e4f44..318a65632 100644 --- a/modules/pcjs/lib/memory.js +++ b/modules/pcjs/lib/memory.js @@ -64,7 +64,7 @@ if (typeof module !== 'undefined') { */ /** - * Memory(addr, size, fReadOnly, controller) + * Memory(addr, size, type, controller) * * The Bus component allocates Memory objects so that each has a memory buffer with a * block-granular starting address and an address range equal to bus.blockSize; however, @@ -74,40 +74,42 @@ if (typeof module !== 'undefined') { * The Bus allocates empty blocks for the entire address space during initialization, so that * any reads/writes to undefined addresses will have no effect. Later, the ROM and RAM * components will ask the Bus to allocate memory for specific ranges, and the Bus will allocate - * as many new BLOCK_SIZE Memory objects as the ranges require. Partial Memory blocks could be - * supported in theory, but in practice, they're not. + * as many new BLOCK_SIZE Memory objects as the ranges require. Partial Memory blocks could + * also be supported in theory, but in practice, they're not. * * Because Memory blocks now allow us to have a "sparse" address space, we could choose to * take the memory hit of allocating 4K arrays per block, where each element stores only one byte, * instead of the more frugal but slightly slower approach of allocating arrays of 32-bit dwords - * (NUMARRAYS) and shifting/masking bytes/words to/from dwords; in theory, byte accesses would + * (DWORDARRAYS) and shifting/masking bytes/words to/from dwords; in theory, byte accesses would * be faster and word accesses somewhat less faster. * * However, preliminary testing of that feature (FATARRAYS) did not yield significantly faster * performance, so it is OFF by default to minimize our memory consumption. Using TYPEDARRAYS * would seem best, but as discussed in defines.js, it's off by default, because it doesn't perform - * as well as NUMARRAYS; the other advantage of TYPEDARRAYS is that it should theoretically use - * about 1/2 the memory of NUMARRAYS (32-bit elements vs 64-bit numbers), but I value speed over size - * at this point. Also, not all JavaScript implementations support TYPEDARRAYS (IE9 is probably + * as well as DWORDARRAYS; the other advantage of TYPEDARRAYS is that it should theoretically use + * about 1/2 the memory of DWORDARRAYS (32-bit elements vs 64-bit numbers), but I value speed over + * size at this point. Also, not all JavaScript implementations support TYPEDARRAYS (IE9 is probably * the only real outlier: it lacks typed arrays but otherwise has all the necessary HTML5 support). * * WARNING: Since Memory blocks are low-level objects that have no UI requirements, they - * do not inherit from the Component class, so you should only use class methods of Component, - * such as Component.assert(), or Debugger methods if a debugger (dbg) is available. + * do not inherit from the Component class, so if you want to use any Component class methods, + * such as Component.assert(), use the corresponding Debugger methods instead (assuming a debugger + * is available). * * @constructor * @param {number} addr of block (must be some multiple of bus.blockSize) * @param {number} [size] of block's buffer in bytes (0 for none); must be a multiple of 4 - * @param {boolean} [fReadOnly] is true if the block must be marked read-only + * @param {number} [type] is one of the Memory.TYPE constants (default is Memory.TYPE.NONE) * @param {Object} [controller] is an optional memory controller component */ -function Memory(addr, size, fReadOnly, controller) +function Memory(addr, size, type, controller) { var i; this.cb = size || 0; this.adw = null; this.offset = 0; - this.fReadOnly = fReadOnly; + this.type = type || Memory.TYPE.NONE; + this.fReadOnly = (type == Memory.TYPE.ROM); this.controller = null; this.fDirty = this.fDirtyEver = false; @@ -179,6 +181,28 @@ function Memory(addr, size, fReadOnly, controller) } } +/* + * Basic memory types + * + * The type that is most critical is ROM, because it determines the fReadOnly setting for allocated + * memory blocks. Both RAM and VIDEO memory are always considered writable, and even ROM can be written + * using the Bus setByteDirect() interface (which in turn uses the Memory writeByteDirect() interface), + * allowing the ROM component to initialize its own memory. Only the Memory interfaces used by the CPU + * are designed to ignore writes to ROM. + * + * The other purpose these types serve is to provide the Control Panel with the ability to highlight + * memory regions according to their primary purpose. + * + * Unallocated regions of the address space also contain memory blocks, but the blocks themselves are + * empty (that is, their data arrays are uninitialized) and the memory type is NONE. + */ +Memory.TYPE = { + NONE: 0, + RAM: 1, + ROM: 2, + VIDEO: 3 +}; + /** * readNone(off) * diff --git a/modules/pcjs/lib/panel.js b/modules/pcjs/lib/panel.js index 8a72876cd..ed69c399c 100644 --- a/modules/pcjs/lib/panel.js +++ b/modules/pcjs/lib/panel.js @@ -48,6 +48,8 @@ if (typeof module !== 'undefined') { */ function Panel(parmsPanel) { Component.call(this, "Panel", parmsPanel, Panel); + this.canvas = null; + if (BACKTRACK) this.fBackTrack = false; } Component.subclass(Component, Panel); @@ -55,9 +57,9 @@ Component.subclass(Component, Panel); /** * setBinding(sHTMLType, sBinding, control) * - * The Panel doesn't have any bindings of its own; it passes along all binding requests to - * the Computer, CPU, Keyboard and Debugger components. The order shouldn't matter, since any - * component that doesn't recognize the specified binding should simply ignore it. + * Most panel layouts don't have bindings of their own, so we pass along all binding requests to the + * Computer, CPU, Keyboard and Debugger components first. The order shouldn't matter, since any component + * that doesn't recognize the specified binding should simply ignore it. * * @this {Panel} * @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas") @@ -71,6 +73,21 @@ Panel.prototype.setBinding = function(sHTMLType, sBinding, control) if (this.cpu && this.cpu.setBinding(sHTMLType, sBinding, control)) return true; if (this.kbd && this.kbd.setBinding(sHTMLType, sBinding, control)) return true; if (DEBUGGER && this.dbg && this.dbg.setBinding(sHTMLType, sBinding, control)) return true; + if (!this.canvas && sHTMLType == "canvas") { + var fPanel = false; + if (BACKTRACK && sBinding == "btpanel") { + this.fBackTrack = fPanel = true; + } + if (fPanel) { + this.canvas = control; + this.canvasContext = this.canvas.getContext("2d"); + /* + * this.canvas.width and this.canvas.height contain the width and height of the canvas, in pixels + */ + this.fRedraw = true; + return true; + } + } return this.parent.setBinding.call(this, sHTMLType, sBinding, control); }; @@ -86,6 +103,7 @@ Panel.prototype.setBinding = function(sHTMLType, sBinding, control) Panel.prototype.initBus = function(cmp, bus, cpu, dbg) { this.cmp = cmp; + this.bus = bus; this.cpu = cpu; this.dbg = dbg; this.kbd = cmp.getComponentByType("Keyboard"); @@ -119,6 +137,41 @@ Panel.prototype.powerDown = function(fSave) return true; }; +/** + * updateAnimation() + * + * If the given Control Panel contains a canvas requiring animation (eg, "btpanel"), then this is where that happens. + * + * @this {Panel} + */ +Panel.prototype.updateAnimation = function() +{ + var context = this.canvasContext; + if (context) { + if (this.fRedraw) { + if (BACKTRACK && this.fBackTrack) { + context.font = "40px Arial"; + context.fillStyle = "#FFFFFF"; + context.fillText("BackTrack Panel",10,50); + } + this.fRedraw = false; + } + } +}; + +/** + * updateStatus() + * + * Update function for Control Panels containing DOM elements with low-frequency display requirements. + * + * For the time being, the X86CPU component has its own updateStatus() handler, and displays all CPU registers itself. + * + * @this {Panel} + */ +Panel.prototype.updateStatus = function() +{ +}; + /** * Panel.init() * @@ -130,9 +183,9 @@ Panel.prototype.powerDown = function(fSave) * NOTE: Unlike most other component init() functions, this one is designed to be * called multiple times: once at load time, so that we can binding our print() * function to the panel's output control ASAP, and again when the Computer component - * is verifying that all components are ready and invoking their setPower() functions. + * is verifying that all components are ready and invoking their powerUp() functions. * - * Our setPower() method gives us a second opportunity to notify any components that + * Our powerUp() method gives us a second opportunity to notify any components that * that might care (eg, CPU, Keyboard, and Debugger) that we have some controls they * might want to use. */ diff --git a/modules/pcjs/lib/ram.js b/modules/pcjs/lib/ram.js index e5718255a..7be38a25a 100644 --- a/modules/pcjs/lib/ram.js +++ b/modules/pcjs/lib/ram.js @@ -35,6 +35,7 @@ if (typeof module !== 'undefined') { var web = require("../../shared/lib/weblib"); var Component = require("../../shared/lib/component"); + var Memory = require("./memory"); var ROM = require("./rom"); } @@ -47,7 +48,7 @@ if (typeof module !== 'undefined') { * size: amount of RAM, in bytes (optional) * * NOTE: We make a note of the specified size, but no memory is initially allocated - * for the RAM until the Computer component calls setPower(). + * for the RAM until the Computer component calls powerUp(). * * @constructor * @extends Component @@ -157,7 +158,7 @@ RAM.prototype.reset = function() { this.sizeRAM = baseRAM; } if (!this.fAllocated && this.sizeRAM) { - if (this.bus.addMemory(this.addrRAM, this.sizeRAM)) { + if (this.bus.addMemory(this.addrRAM, this.sizeRAM, Memory.TYPE.RAM)) { this.fAllocated = true; this.status(Math.floor(this.sizeRAM / 1024) + "Kb"); /* diff --git a/modules/pcjs/lib/rom.js b/modules/pcjs/lib/rom.js index 1e42675f0..72316c4fa 100644 --- a/modules/pcjs/lib/rom.js +++ b/modules/pcjs/lib/rom.js @@ -37,6 +37,7 @@ if (typeof module !== 'undefined') { var web = require("../../shared/lib/weblib"); var DumpAPI = require("../../shared/lib/dumpapi"); var Component = require("../../shared/lib/component"); + var Memory = require("./memory"); } /** @@ -315,7 +316,7 @@ ROM.prototype.copyROM = function() ROM.prototype.addROM = function(addr) { if (addr == null) return true; - if (this.bus.addMemory(addr, this.sizeROM, true)) { + if (this.bus.addMemory(addr, this.sizeROM, Memory.TYPE.ROM)) { if (DEBUG) this.log("addROM(): copying ROM to " + str.toHexAddr(addr) + " (0x" + str.toHex(this.abROM.length) + " bytes)"); var bto = null; for (var off = 0; off < this.abROM.length; off++) { diff --git a/modules/pcjs/lib/video.js b/modules/pcjs/lib/video.js index 2d031f6e6..ca6af7967 100644 --- a/modules/pcjs/lib/video.js +++ b/modules/pcjs/lib/video.js @@ -37,6 +37,7 @@ if (typeof module !== 'undefined') { var web = require("../../shared/lib/weblib"); var DumpAPI = require("../../shared/lib/dumpapi"); var Component = require("../../shared/lib/component"); + var Memory = require("./memory"); var Messages = require("./messages"); var ChipSet = require("./chipset"); var Keyboard = require("./keyboard"); @@ -200,18 +201,20 @@ function Video(parmsVideo, canvas, context, textarea, container) this.container = container; if (this.container) { this.container.doFullScreen = container['requestFullscreen'] || container['msRequestFullscreen'] || container['mozRequestFullScreen'] || container['webkitRequestFullscreen']; - var onFullScreenChange = function() { - var fFullScreen = (document['fullscreenElement'] || document['mozFullScreenElement'] || document['webkitFullscreenElement'] || document['msFullscreenElement']); - video.notifyFullScreen(fFullScreen? true : false); - }; - if ('onfullscreenchange' in document) { - document.addEventListener('fullscreenchange', onFullScreenChange, false); - } else if ('onmozfullscreenchange' in document) { - document.addEventListener('mozfullscreenchange', onFullScreenChange, false); - } else if ('onwebkitfullscreenchange' in document) { - document.addEventListener('webkitfullscreenchange', onFullScreenChange, false); - } else if ('onmsfullscreenchange' in document) { - document.addEventListener('msfullscreenchange', onFullScreenChange, false); + if (this.container.doFullScreen) { + var onFullScreenChange = function() { + var fFullScreen = (document['fullscreenElement'] || document['mozFullScreenElement'] || document['webkitFullscreenElement'] || document['msFullscreenElement']); + video.notifyFullScreen(fFullScreen? true : false); + }; + if ('onfullscreenchange' in document) { + document.addEventListener('fullscreenchange', onFullScreenChange, false); + } else if ('onmozfullscreenchange' in document) { + document.addEventListener('mozfullscreenchange', onFullScreenChange, false); + } else if ('onwebkitfullscreenchange' in document) { + document.addEventListener('webkitfullscreenchange', onFullScreenChange, false); + } else if ('onmsfullscreenchange' in document) { + document.addEventListener('msfullscreenchange', onFullScreenChange, false); + } } } @@ -227,19 +230,21 @@ function Video(parmsVideo, canvas, context, textarea, container) }; this.inputScreen.lockPointer = this.inputScreen['requestPointerLock'] || this.inputScreen['mozRequestPointerLock'] || this.inputScreen['webkitRequestPointerLock']; this.inputScreen.unlockPointer = this.inputScreen['exitPointerLock'] || this.inputScreen['mozExitPointerLock'] || this.inputScreen['webkitExitPointerLock']; - var onPointerLockChange = function() { - var fLocked = ( - document['pointerLockElement'] === video.inputScreen || - document['mozPointerLockElement'] === video.inputScreen || - document['webkitPointerLockElement'] === video.inputScreen); - video.notifyPointerLocked(fLocked); - }; - if ('onpointerlockchange' in document) { - document.addEventListener('pointerlockchange', onPointerLockChange, false); - } else if ('onmozpointerlockchange' in document) { - document.addEventListener('mozpointerlockchange', onPointerLockChange, false); - } else if ('onwebkitpointerlockchange' in document) { - document.addEventListener('webkitpointerlockchange', onPointerLockChange, false); + if (this.inputScreen.lockPointer) { + var onPointerLockChange = function() { + var fLocked = ( + document['pointerLockElement'] === video.inputScreen || + document['mozPointerLockElement'] === video.inputScreen || + document['webkitPointerLockElement'] === video.inputScreen); + video.notifyPointerLocked(fLocked); + }; + if ('onpointerlockchange' in document) { + document.addEventListener('pointerlockchange', onPointerLockChange, false); + } else if ('onmozpointerlockchange' in document) { + document.addEventListener('mozpointerlockchange', onPointerLockChange, false); + } else if ('onwebkitpointerlockchange' in document) { + document.addEventListener('webkitpointerlockchange', onPointerLockChange, false); + } } } @@ -3791,7 +3796,7 @@ Video.prototype.setMode = function(nMode, fForce) var controller = (card === this.cardEGA? card : null); - if (!this.bus.addMemory(card.addrBuffer, card.sizeBuffer, false, controller)) { + if (!this.bus.addMemory(card.addrBuffer, card.sizeBuffer, Memory.TYPE.VIDEO, controller)) { /* * TODO: Force this failure case and see how well the Video component deals with it. */ @@ -4041,7 +4046,7 @@ Video.prototype.updateScreen = function(fForce) } else { /* - * This should never happen, but since updateScreen() is also called by CPU.displayVideo(), + * This should never happen, but since updateScreen() is also called by CPU.updateVideo(), * better safe than sorry. */ if (this.aCellCache === undefined) return; @@ -4306,24 +4311,23 @@ Video.prototype.updateScreenGraphicsEGA = function(addrScreen, addrScreenLimit) this.aCellCache[iCell] = data; if (x < xDirty) xDirty = x; for (var iPixel = 0; iPixel < nPixelsPerCell; iPixel++) { - var dwPixel = data & 0x80808080; /* * JavaScript Alert: if adwMemory contains a 32-bit value such as -1526726656, and then we mask it * with 0x80808080, we end up with -2147483648, which in a perfect 32-bit world, would be equivalent * to 0x80000000, which means that when we look up "Video.aEGADWToByte[0x80000000]", we should get * the entry containing 0x8. But no, in JavaScript, since the original value was negative, the * masked value is still negative, because there are 52 "significand" bits in JavaScript numbers, - * whereas bit-wise operations operate ONLY on the low 32 bits. + * and bit-wise operations operate ONLY on the low 32 bits, leaving the higher sign bits intact. * - * This can be confirmed by looking at dwPixel.toString(16), which returns "-80000000". The solution - * is to add 4294967296 (0x100000000) to any negative 32-bit value for which you need the positive - * representation instead. + * This can be confirmed by looking at dwPixel.toString(16), which returns "-80000000". One solution + * is to add 4294967296 (0x100000000) to any negative 32-bit value for which we need the positive + * representation. + * + * And, since assertions don't fix problems (only catch them, and only in DEBUG builds), I'm also + * ensuring that bPixel will always default to 0 if an undefined value ever slips through again. */ + var dwPixel = data & 0x80808080; if (dwPixel < 0) dwPixel += 0x100000000; - /* - * Since assertions don't fix problems (only catch them, and only in DEBUG builds), I'm also ensuring - * that bPixel will always default to 0 if an undefined value ever slips through again. - */ this.assert(Video.aEGADWToByte[dwPixel] !== undefined); var bPixel = Video.aEGADWToByte[dwPixel] || 0; this.setPixel(this.imageScreenBuffer, x++, y, aPixelColors[bPixel]); @@ -5238,12 +5242,8 @@ Video.init = function() * HACK: A canvas style of "auto" provides for excellent responsive canvas scaling in EVERY browser * except IE9/IE10, so I recalculate the appropriate CSS height every time the parent DIV is resized; * IE11 works without this hack, so we take advantage of the fact that IE11 doesn't report itself as "MSIE". - * - * Also, make sure the parent DIV also has a style of "auto"; normally, it has no explicit height, but - * sometimes we'll preset it to a height (eg, "350px") for design purposes. */ - eCanvas.style.height = eVideo.style.height = "auto"; - + eCanvas.style.height = "auto"; if (web.getUserAgent().indexOf("MSIE") >= 0) { eCanvas.style.height = (((eVideo.clientWidth * parmsVideo['screenHeight']) / parmsVideo['screenWidth']) | 0) + "px"; eVideo.onresize = function(eParent, eChild, cx, cy) { @@ -5255,24 +5255,25 @@ Video.init = function() eVideo.appendChild(eCanvas); /* - * HACK: Android-based browsers (eg, the Kindle Fire browser, the Chrome browser) don't honor the + * HACK: Android-based browsers, like the Silk (Amazon) browser and Chrome for Android, don't honor the * "contenteditable" attribute; that is, when the canvas receives focus, they don't activate the on-screen * keyboard. So my fallback is to create a transparent textarea on top of the canvas. * * The parent DIV must have a style of "position:relative" (alternatively, a class of "pcjs-container"), * so that we can position the textarea using absolute coordinates. Also, we don't want the textarea to be * visible, but we must use "opacity:0" instead of "visibility:hidden", because the latter seems to prevent - * the element from receiving events. + * the element from receiving events. These styling requirements are taken care of in components.css + * (see references to the "pcjs-video-object" class). * * UPDATE: Unfortunately, Android keyboards like to compose whole words before transmitting any of the * intervening characters; our textarea's keyDown/keyUp event handlers DO receive intervening key events, * but their keyCode property is ZERO. Virtually the only usable key event we receive is the Enter key. - * Android users will have to use machines that include their own on-screen "soft keyboard", or use an external - * keyboard. + * Android users will have to use machines that include their own on-screen "soft keyboard", or use an + * external keyboard. * - * The following code didn't work any better on Android. You could clearly see the overlaid semi-transparent - * password-enabled input field, but none of the input characters were passed along, with the exception of the - * "Go" (Enter) key. + * The following attempt to use a password-enabled input field didn't work any better on Android. You could + * clearly see the overlaid semi-transparent input field, but none of the input characters were passed along, + * with the exception of the "Go" (Enter) key. * * var eInput = window.document.createElement("input"); * eInput.setAttribute("type", "password"); @@ -5280,14 +5281,8 @@ Video.init = function() * eVideo.appendChild(eInput); * * See this Chromium issue for more information: https://code.google.com/p/chromium/issues/detail?id=118639 - * - * TODO: The necessary styles for both the "textarea" and the parent video "object div" should be moved to the - * "component.css" file; they're here only for faster testing. - * - * NOTE: The "line-height:0" attribute is how I prevent Safari on iOS from always displaying a blinking cursor. */ var eTextArea = window.document.createElement("textarea"); - eTextArea.setAttribute("style", "position:absolute; left:0; top:0; width:100%; height:100%; opacity:0; border:0; padding:0; line-height:0;"); /* * As noted in keyboard.js, the keyboard on an iOS device pops up with the SHIFT key depressed, @@ -5297,9 +5292,6 @@ Video.init = function() eTextArea.setAttribute("autocapitalize", "off"); eTextArea.setAttribute("autocorrect", "off"); } - - eVideo.style.clear = "both"; - eVideo.style.position = "relative"; eVideo.appendChild(eTextArea); /* diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index 6bbe4baec..130860995 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -125,7 +125,7 @@ function X86CPU(parmsCPU) { * [0]: registered component * [1]: registered function to call for every software interrupt * - * The registered function is called with the physical address (EIP) following the software interrupt; + * The registered function is called with the linear address (LIP) following the software interrupt; * if any function returns false, the software interrupt will be skipped (presumed to be emulated), * and no further notification functions will be called. * @@ -157,6 +157,11 @@ function X86CPU(parmsCPU) { this.nBurstCycles = 0; this.aFlags.fComplete = this.aFlags.fDebugCheck = false; + /* + * If there are no live registers to display, then updateStatus() can skip a bit.... + */ + this.cLiveRegs = 0; + /* * We're just declaring aMemBlocks and associated Bus parameters here; they'll be initialized by initMemory() * when the Bus is initialized. @@ -171,7 +176,7 @@ function X86CPU(parmsCPU) { if (SAMPLER) { /* - * For now, we're just going to sample EIP values (well, EIP + cycle count) + * For now, we're just going to sample LIP values (well, LIP + cycle count) */ this.nSamples = 50000; this.nSampleFreq = 1000; @@ -1031,7 +1036,7 @@ X86CPU.prototype.getChecksum = function() * @this {X86CPU} * @param {number} nInt * @param {Component} component - * @param {function(number)} fn is called with the EIP value following the software interrupt + * @param {function(number)} fn is called with the LIP value following the software interrupt */ X86CPU.prototype.addIntNotify = function(nInt, component, fn) { @@ -1430,7 +1435,7 @@ X86CPU.prototype.setCSIP = function(off, sel, fCall) this.assert((off & 0xffff) == off); this.segCS.fCall = fCall; /* - * We break this operation into the following discrete steps (eg, set IP, load CS, and then update EIP) + * We break this operation into the following discrete steps (eg, set IP, load CS, and then update LIP) * so that segCS.load(sel) has the option of modifying IP when sel refers to a gate (call, interrupt, trap, etc). */ this.regEIP = off; @@ -1875,6 +1880,7 @@ X86CPU.prototype.setBinding = function(sHTMLType, sBinding, control) case "D": case "V": this.bindings[sBinding] = control; + this.cLiveRegs++; fBound = true; break; default: @@ -2578,41 +2584,52 @@ X86CPU.prototype.delayINTR = function() }; /** - * displayStatus() + * updateStatus() + * + * This provides periodic Control Panel updates (eg, a few times per second; see STATUS_UPDATES_PER_SECOND). + * this is where we take care of any DOM updates (eg, register values) while the CPU is running. + * + * Any high-frequency updates should be performed in updateVideo(), which should avoid DOM updates, since + * updateVideo() can be called up to 60 times per second (see VIDEO_UPDATES_PER_SECOND). * * @this {X86CPU} - * @param {boolean} [fForce] + * @param {boolean} [fForce] (true will display registers even if the CPU is running and "live" registers are not enabled) */ -X86CPU.prototype.displayStatus = function(fForce) +X86CPU.prototype.updateStatus = function(fForce) { - if (fForce || !this.aFlags.fRunning || this.aFlags.fDisplayLiveRegs) { - this.displayReg("AX", this.regEAX); - this.displayReg("BX", this.regEBX); - this.displayReg("CX", this.regECX); - this.displayReg("DX", this.regEDX); - this.displayReg("SP", this.regESP); - this.displayReg("BP", this.regEBP); - this.displayReg("SI", this.regESI); - this.displayReg("DI", this.regEDI); - this.displayReg("CS", this.segCS.sel); - this.displayReg("DS", this.segDS.sel); - this.displayReg("SS", this.segSS.sel); - this.displayReg("ES", this.segES.sel); - this.displayReg("IP", this.regEIP); - var regPS = this.getPS(); - this.displayReg("PS", regPS); - this.displayReg("C", (regPS & X86.PS.CF)? 1 : 0, 1); - this.displayReg("P", (regPS & X86.PS.PF)? 1 : 0, 1); - this.displayReg("A", (regPS & X86.PS.AF)? 1 : 0, 1); - this.displayReg("Z", (regPS & X86.PS.ZF)? 1 : 0, 1); - this.displayReg("S", (regPS & X86.PS.SF)? 1 : 0, 1); - this.displayReg("T", (regPS & X86.PS.TF)? 1 : 0, 1); - this.displayReg("I", (regPS & X86.PS.IF)? 1 : 0, 1); - this.displayReg("D", (regPS & X86.PS.DF)? 1 : 0, 1); - this.displayReg("V", (regPS & X86.PS.OF)? 1 : 0, 1); + if (this.cLiveRegs) { + if (fForce || !this.aFlags.fRunning || this.aFlags.fDisplayLiveRegs) { + this.displayReg("AX", this.regEAX); + this.displayReg("BX", this.regEBX); + this.displayReg("CX", this.regECX); + this.displayReg("DX", this.regEDX); + this.displayReg("SP", this.regESP); + this.displayReg("BP", this.regEBP); + this.displayReg("SI", this.regESI); + this.displayReg("DI", this.regEDI); + this.displayReg("CS", this.segCS.sel); + this.displayReg("DS", this.segDS.sel); + this.displayReg("SS", this.segSS.sel); + this.displayReg("ES", this.segES.sel); + this.displayReg("IP", this.regEIP); + var regPS = this.getPS(); + this.displayReg("PS", regPS); + this.displayReg("C", (regPS & X86.PS.CF)? 1 : 0, 1); + this.displayReg("P", (regPS & X86.PS.PF)? 1 : 0, 1); + this.displayReg("A", (regPS & X86.PS.AF)? 1 : 0, 1); + this.displayReg("Z", (regPS & X86.PS.ZF)? 1 : 0, 1); + this.displayReg("S", (regPS & X86.PS.SF)? 1 : 0, 1); + this.displayReg("T", (regPS & X86.PS.TF)? 1 : 0, 1); + this.displayReg("I", (regPS & X86.PS.IF)? 1 : 0, 1); + this.displayReg("D", (regPS & X86.PS.DF)? 1 : 0, 1); + this.displayReg("V", (regPS & X86.PS.OF)? 1 : 0, 1); + } } + var controlSpeed = this.bindings["speed"]; if (controlSpeed) controlSpeed.textContent = this.getSpeedCurrent(); + + this.parent.updateStatus.call(this, fForce); }; /** @@ -2770,7 +2787,7 @@ X86CPU.prototype.stepCPU = function(nMinCycles) var n = this.aSamples[this.iSampleNext]; if (n !== -1) { if (n !== t) { - this.println("sample deviation at index " + this.iSampleNext + ": current EIP=" + str.toHex(this.regLIP)); + this.println("sample deviation at index " + this.iSampleNext + ": current LIP=" + str.toHex(this.regLIP)); this.stopCPU(); break; } diff --git a/modules/pcjs/lib/x86help.js b/modules/pcjs/lib/x86help.js index 45c9b0e18..cb506d4be 100644 --- a/modules/pcjs/lib/x86help.js +++ b/modules/pcjs/lib/x86help.js @@ -348,9 +348,9 @@ var X86Help = { */ opHelpXCHGrb: function(dst, src) { if (this.regEA < 0) { - // - // Decode which register was src - // + /* + * Decode which register was src + */ switch (this.bModRM & 0x7) { case 0x0: // AL this.regEAX = (this.regEAX & ~0xff) | dst; @@ -381,11 +381,11 @@ var X86Help = { } this.nStepCycles -= this.CYCLES.nOpCyclesXchgRR; } else { - // - // This is a case where the ModRM decoder that's calling us didn't know it should have called modEAByte() - // instead of getEAByte(), so we compensate by updating regEAWrite. However, setEAByte() has since been - // changed to revalidate the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary. - // + /* + * This is a case where the ModRM decoder that's calling us didn't know it should have called modEAByte() + * instead of getEAByte(), so we compensate by updating regEAWrite. However, setEAByte() has since been + * changed to revalidate the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary. + */ this.regEAWrite = this.regEA; this.setEAByte(dst); this.nStepCycles -= this.CYCLES.nOpCyclesXchgRM; @@ -410,9 +410,9 @@ var X86Help = { */ opHelpXCHGrw: function(dst, src) { if (this.regEA < 0) { - // - // Decode which register was src - // + /* + * Decode which register was src + */ switch (this.bModRM & 0x7) { case 0x0: // AX this.regEAX = dst; @@ -443,11 +443,11 @@ var X86Help = { } this.nStepCycles -= this.CYCLES.nOpCyclesXchgRR; } else { - // - // This is a case where the ModRM decoder that's calling us didn't know it should have called modEAWord() - // instead of getEAWord(), so we compensate by updating regEAWrite. However, setEAWord() has since been - // changed to revalidate the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary. - // + /* + * This is a case where the ModRM decoder that's calling us didn't know it should have called modEAWord() + * instead of getEAWord(), so we compensate by updating regEAWrite. However, setEAWord() has since been + * changed to revalidate the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary. + */ this.regEAWrite = this.regEA; this.setEAWord(dst); this.nStepCycles -= this.CYCLES.nOpCyclesXchgRM; @@ -539,7 +539,7 @@ var X86Help = { * deals exclusively with IDT descriptors. * * This means we must take care to replicate critical features of setCSIP(); eg, setting segCS.fCall before - * calling loadIDT(), updating EIP, and flushing the prefetch queue. + * calling loadIDT(), updating LIP, and flushing the prefetch queue. * * @this {X86CPU} * @param {number} nIDT @@ -714,7 +714,7 @@ var X86Help = { /* * Similarly, the PC AT ROM BIOS deliberately generates a couple of GP faults as part of the POST * (Power-On Self Test); we don't want to ignore those, but we don't want to halt on them either. We - * detect those faults by virtue of EIP being in the range %0F0000 to %0FFFFF. + * detect those faults by virtue of the LIP being in the range %0F0000 to %0FFFFF. */ if (this.regLIP >= 0x0F0000 && this.regLIP <= 0x0FFFFF) { fHalt = false; diff --git a/modules/pcjs/templates/components.css b/modules/pcjs/templates/components.css index a17612970..78b69f08d 100644 --- a/modules/pcjs/templates/components.css +++ b/modules/pcjs/templates/components.css @@ -109,6 +109,22 @@ line-height: 19px; /* the equivalent of "vertical-align: middle" for single-line elements */ background-color: #000000; } +.pcjs-video-object { + clear: both; + height: auto; + position: relative; +} +.pcjs-video-object textarea { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + opacity: 0; + border: 0; + padding: 0; + line-height: 0; /* try to prevent Safari on iOS from always displaying a blinking cursor */ +} .pcjs-reference { float: left; font-size: x-small; diff --git a/modules/shared/lib/component.js b/modules/shared/lib/component.js index ce61dac74..b8302bbee 100644 --- a/modules/shared/lib/component.js +++ b/modules/shared/lib/component.js @@ -838,7 +838,7 @@ Component.prototype = { if (this.aFlags.fReady) { fnReady(); } else { - if (DEBUG) this.log("NOT ready"); + if (MAXDEBUG) this.log("NOT ready"); this.fnReady = fnReady; } } @@ -856,7 +856,7 @@ Component.prototype = { if (!this.aFlags.fError) { this.aFlags.fReady = (fReady !== false); if (this.aFlags.fReady) { - if (DEBUG || this.name) this.log("ready"); + if (MAXDEBUG /* || this.name */) this.log("ready"); var fnReady = this.fnReady; this.fnReady = null; if (fnReady) fnReady(); diff --git a/modules/shared/lib/weblib.js b/modules/shared/lib/weblib.js index 8f1e9d348..4139a625b 100644 --- a/modules/shared/lib/weblib.js +++ b/modules/shared/lib/weblib.js @@ -223,7 +223,7 @@ web.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNoti * from the local file system (ie, when using the "file:" protocol), we have to be a bit more "flexible". */ if (xmlHTTP.status == 200 || !xmlHTTP.status && sURLData.length && web.getHostProtocol() == "file:") { - web.log("xmlHTTP.onreadystatechange(" + sURL + "): returned " + sURLData.length + " bytes"); + if (MAXDEBUG) web.log("xmlHTTP.onreadystatechange(" + sURL + "): returned " + sURLData.length + " bytes"); } else { nErrorCode = xmlHTTP.status || -1; @@ -247,12 +247,12 @@ web.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNoti sData += p + '=' + encodeURIComponent(data[p]); } sData = sData.replace(/%20/g, '+'); - web.log("web.loadResource(POST " + sURL + "): " + sData.length + " bytes"); + if (MAXDEBUG) web.log("web.loadResource(POST " + sURL + "): " + sData.length + " bytes"); xmlHTTP.open("POST", sURL, fAsync); xmlHTTP.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xmlHTTP.send(sData); } else { - web.log("web.loadResource(GET " + sURL + ")"); + if (MAXDEBUG) web.log("web.loadResource(GET " + sURL + ")"); xmlHTTP.open("GET", sURL, fAsync); xmlHTTP.send(); } @@ -260,7 +260,7 @@ web.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNoti if (!fAsync) { sURLData = xmlHTTP.responseText; if (xmlHTTP.status == 200) { - web.log("web.loadResource(" + sURL + "): returned " + sURLData.length + " bytes"); + if (MAXDEBUG) web.log("web.loadResource(" + sURL + "): returned " + sURLData.length + " bytes"); } else { nErrorCode = xmlHTTP.status || -1; web.log("web.loadResource(" + sURL + "): error code " + nErrorCode);