Fixed localStorage availability test (for real this time), and asynchronified embed.js
I spent some time fixing the latter because browsers have started warning that synchronous XMLHttpRequest calls on the "main thread" (as Firefox calls it) are deprecated. Since the days of such calls seems to be numbered, I decided to act sooner rather than later. All PCjs XMLHttpRequests go through a single library function (weblib.loadResource), which accepts an fAsync parameter, and the only code that is still allowed to pass false for that parameter are "emergency" shutdown requests (which I believe browsers must still allow).
This commit is contained in:
parent
024f9fccf8
commit
e0487bccd8
9 changed files with 1397 additions and 1245 deletions
|
|
@ -1,7 +1,6 @@
|
|||
JavaScript Negativity
|
||||
---
|
||||
For those of us coming from the world of C, it's easy to be "negative" about the way JavaScript deals with 32-bit
|
||||
integers.
|
||||
Coming from the C programming language, it's easy to be "negative" about how JavaScript deals with 32-bit integers.
|
||||
|
||||
As a newcomer, you quickly learn that JavaScript supports only one numeric data type -- 64-bit floats -- and you groan.
|
||||
|
||||
|
|
@ -9,9 +8,9 @@ Then you learn that all the "bitwise" operators (**~**, **|**, **&**, **^**, **&
|
|||
**>>>**) treat their operands as 32-bit integer values and produce 32-bit integer results, and you breathe
|
||||
a sigh of relief.
|
||||
|
||||
But then you start noticing oddities. In 32-bit C programming, you can take any 32-bit value,
|
||||
such as -1526726656 (which is equivalent to 0xA5000000), mask it with 0x80808080, and get 0x80000000. However,
|
||||
in JavaScript, you actually get -0x80000000, which, sadly, is not equal to 0x80000000.
|
||||
But then you start noticing oddities. In C, you can take any 32-bit value, such as -1526726656 (which is equivalent
|
||||
to 0xA5000000), mask it with 0x80808080, and get 0x80000000. However, in JavaScript, you actually get -0x80000000,
|
||||
which, sadly, is not equal to 0x80000000.
|
||||
|
||||
To verify, type the following into any JavaScript REPL (eg, Node):
|
||||
|
||||
|
|
@ -22,28 +21,28 @@ To verify, type the following into any JavaScript REPL (eg, Node):
|
|||
> n.toString(16)
|
||||
'-80000000'
|
||||
|
||||
So the notion that bitwise operators yield 32-bit results isn't exactly right; every result continues to be
|
||||
sign-extended into the entire 52 "significand" bits of the underlying 64-bit float. And it's impossible to simply
|
||||
"mask away" those unwanted sign bits, thanks to the fundamental restriction of JavaScript bitwise operators:
|
||||
they operate *only* on the low 32 bits.
|
||||
So the notion that bitwise operators yield 32-bit results isn't exactly right; the sign (bit 31) of every 32-bit
|
||||
result is always extended into the entire 52 "significand" bits of the underlying 64-bit float. And it's
|
||||
impossible to simply "mask away" those additional sign bits, thanks to the fundamental restriction of JavaScript bitwise
|
||||
operators: they operate *only* on the low 32 bits.
|
||||
|
||||
If you really want 0x80000000 instead of -0x80000000, add 0x100000000:
|
||||
The easiest way to remove the high-order sign bits from a negative 32-bit value is to add 0x100000000:
|
||||
|
||||
> n = (n < 0? n + 0x100000000 : n)
|
||||
2147483648
|
||||
> n.toString(16)
|
||||
'80000000'
|
||||
|
||||
This works because JavaScript is perfectly capable of representing 0x80000000 as a positive number.
|
||||
But be careful, because as soon as you perform *any* bitwise operation on a value with bit 31 set, even
|
||||
something as innocuous-looking as:
|
||||
This works because JavaScript is perfectly capable of representing 0x80000000, or any other 32-bit value, as a positive
|
||||
number. But be careful, because as soon as you perform *any* bitwise operation on a value with bit 31 set, even
|
||||
an operation as innocuous-looking as:
|
||||
|
||||
> n |= 0
|
||||
-2147483648
|
||||
> n.toString(16)
|
||||
'-80000000'
|
||||
|
||||
Viola: instant negative number! To continue the fun, now "or" a one into bit 0:
|
||||
Viola: instant negative number! To continue the fun, now "or" one into bit 0:
|
||||
|
||||
> n |= 1
|
||||
-2147483647
|
||||
|
|
@ -53,7 +52,7 @@ Viola: instant negative number! To continue the fun, now "or" a one into bit 0:
|
|||
Viola: all low 32 bits have instantly flipped!
|
||||
|
||||
Actually, no, this time, I'm pulling your leg. The low 32 bits of the internal value are exactly what you would
|
||||
expect: 0x80000001 (the internal representation would look more like 0xFFFFF80000001). The toString() method is
|
||||
expect: 0x80000001 (the internal representation looks more like 0xFFFFF80000001). The toString() method is
|
||||
just a little misleading. As the MDN docs explain, for a negative number, toString() returns the positive
|
||||
representation of the number, preceded by a - sign, *not* the "two's complement" of the number.
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
"use strict";
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
var web = require("./../../shared/lib/weblib");
|
||||
var Component = require("./../../shared/lib/component");
|
||||
}
|
||||
|
||||
|
|
@ -81,25 +82,6 @@ State.key = function(component, sVersion, sSuffix) {
|
|||
return key;
|
||||
};
|
||||
|
||||
/**
|
||||
* State.localStorage()
|
||||
*
|
||||
* TODO: State's localStorage calls should probably be moved to wrappers in weblib.js, for consistency.
|
||||
*
|
||||
* @return {boolean} true if localStorage available, false if not
|
||||
*/
|
||||
State.localStorage = function() {
|
||||
try {
|
||||
/*
|
||||
* A try/catch block is required, because if the user has disabled localStorage, some browsers feel the need
|
||||
* to throw an exception on any attempt to access it, even when using "typeof".
|
||||
*/
|
||||
return typeof window.localStorage !== 'undefined';
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* State.compress(aSrc)
|
||||
*
|
||||
|
|
@ -269,7 +251,7 @@ State.prototype = {
|
|||
* @return {boolean} true if state exists in localStorage, false if not
|
||||
*
|
||||
* WARNING: Make sure you follow this call with either a call to parse() or unload(),
|
||||
* because any stringified data we've loaded isn't usable until it's been parsed.
|
||||
* because any stringified data that we've loaded isn't usable until it's been parsed.
|
||||
*/
|
||||
load: function(s) {
|
||||
if (s) {
|
||||
|
|
@ -283,8 +265,8 @@ State.prototype = {
|
|||
*/
|
||||
return true;
|
||||
}
|
||||
if (State.localStorage()) {
|
||||
s = window.localStorage.getItem(this.key);
|
||||
if (web.hasLocalStorage()) {
|
||||
s = web.getLocalStorageItem(this.key);
|
||||
if (s) {
|
||||
this[this.id] = s;
|
||||
this.fLoaded = true;
|
||||
|
|
@ -322,19 +304,17 @@ State.prototype = {
|
|||
*/
|
||||
store: function() {
|
||||
var fSuccess = true;
|
||||
if (State.localStorage()) {
|
||||
if (web.hasLocalStorage()) {
|
||||
var s = JSON.stringify(this[this.id]);
|
||||
try {
|
||||
window.localStorage.setItem(this.key, s);
|
||||
if (web.setLocalStorageItem(this.key, s)) {
|
||||
if (DEBUG) this.messageDebugger("localStorage(" + this.key + "): " + s.length + " bytes stored");
|
||||
} catch (e) {
|
||||
} else {
|
||||
/*
|
||||
* WARNING: Because browsers tend to disable all alerts() during an "unload" operation,
|
||||
* it's unlikely anyone will ever see the "quota" errors that occur at this point. Need to
|
||||
* think of some way to notify the user that there's a problem, and offer a way of cleaning
|
||||
* up old states.
|
||||
*/
|
||||
Component.log(e.message || e, "error");
|
||||
Component.error("Unable to store " + s.length + " bytes in browser local storage");
|
||||
fSuccess = false;
|
||||
}
|
||||
|
|
@ -380,17 +360,16 @@ State.prototype = {
|
|||
*/
|
||||
clear: function(fAll) {
|
||||
this.unload();
|
||||
if (State.localStorage()) {
|
||||
var i = 0;
|
||||
while (i < window.localStorage.length) {
|
||||
var key = window.localStorage.key(i++);
|
||||
if (key && (fAll || key.substr(0, this.key.length) == this.key)) {
|
||||
window.localStorage.removeItem(key);
|
||||
if (DEBUG) this.messageDebugger("localStorage(" + key + ") removed");
|
||||
var aKeys = web.getLocalStorageKeys();
|
||||
for (var i = 0; i < aKeys.length; i++) {
|
||||
var sKey = aKeys[i];
|
||||
if (sKey && (fAll || sKey.substr(0, this.key.length) == this.key)) {
|
||||
web.removeLocalStorageItem(sKey);
|
||||
if (DEBUG) this.messageDebugger("localStorage(" + sKey + ") removed");
|
||||
aKeys.splice(i, 1);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* messageDebugger(sMessage)
|
||||
|
|
|
|||
|
|
@ -41,8 +41,23 @@ if (typeof module !== 'undefined') {
|
|||
var web = require("./weblib");
|
||||
}
|
||||
|
||||
/*
|
||||
* We now support asynchronous XML and XSL file loads; simply set fAsync (below) to true.
|
||||
*
|
||||
* NOTE: For that support to work, we have to keep track of the number of machines on the page
|
||||
* (ie, how many embedMachine() calls were issued), reduce the count once the XML for each machine
|
||||
* is fully transformed into HTML, and when the count finally returns to zero, notify all the
|
||||
* machine component init() handlers.
|
||||
*
|
||||
* Also, to prevent those init() handlers from running prematurely, we must disable all page
|
||||
* notification events at the start of the embedding process (web.enablePageEvents(false)) and
|
||||
* re-enable them at the end (web.enablePageEvents(true)).
|
||||
*/
|
||||
var cMachines = 0;
|
||||
var fAsync = true;
|
||||
|
||||
/**
|
||||
* loadXML(sFile, idMachine, sStateFile, fResolve)
|
||||
* loadXML(sFile, idMachine, sStateFile, fResolve, done)
|
||||
*
|
||||
* This is the preferred way to load all XML and XSL files. It uses loadResource()
|
||||
* to load them as strings, which parseXML() can massage before parsing/transforming them.
|
||||
|
|
@ -54,7 +69,7 @@ if (typeof module !== 'undefined') {
|
|||
*
|
||||
* To solve that, I've added an fResolve parameter that tells parseXML() to fetch any
|
||||
* referenced documents ITSELF and insert them into the XML string prior to parsing, instead
|
||||
* of relying on the XSLT template to pull them in. That fetching is handled by resolveRefs(),
|
||||
* of relying on the XSLT template to pull them in. That fetching is handled by resolveXML(),
|
||||
* which iterates over the XML until all "refs" have been resolved (including any nested
|
||||
* references).
|
||||
*
|
||||
|
|
@ -66,40 +81,43 @@ if (typeof module !== 'undefined') {
|
|||
* JavaScript XSLT support. Is it broken, is it a security issue, or am I just calling it wrong?
|
||||
*
|
||||
* @param {string} sXMLFile
|
||||
* @param {string} [idMachine]
|
||||
* @param {string} [sStateFile]
|
||||
* @param {boolean} [fResolve] is true to resolve any "ref" attributes
|
||||
* @return {Array} where [0] contains the unparsed XML string data, and [1] contains a parsed XML object
|
||||
* @param {string|null|undefined} idMachine
|
||||
* @param {string|null|undefined} sStateFile
|
||||
* @param {boolean} fResolve is true to resolve any "ref" attributes
|
||||
* @param {function(string,Object)} done (string contains the unparsed XML string data, and Object contains a parsed XML object)
|
||||
*/
|
||||
function loadXML(sXMLFile, idMachine, sStateFile, fResolve)
|
||||
function loadXML(sXMLFile, idMachine, sStateFile, fResolve, done)
|
||||
{
|
||||
var response = web.loadResource(sXMLFile);
|
||||
if (response[0]) {
|
||||
throw new Error(response[1]);
|
||||
var doneLoadXML = function(sURLName, sXML, nErrorCode) {
|
||||
if (nErrorCode) {
|
||||
done(sXML, null);
|
||||
return;
|
||||
}
|
||||
return parseXML(response[1], sXMLFile, idMachine, sStateFile, fResolve);
|
||||
parseXML(sXML, sXMLFile, idMachine, sStateFile, fResolve, done);
|
||||
};
|
||||
web.loadResource(sXMLFile, fAsync, null, null, doneLoadXML);
|
||||
}
|
||||
|
||||
/**
|
||||
* parseXML(sXML, idMachine, sStateFile, fResolve)
|
||||
* parseXML(sXML, sXMLFile, idMachine, sStateFile, fResolve, done)
|
||||
*
|
||||
* Generates an XML document from an XML string. This function also provides a work-around for XSLT's
|
||||
* lack of support for the document() function (at least on some browsers), by replacing every reference
|
||||
* tag (ie, a tag with a "ref" attribute) with the contents of the referenced file.
|
||||
*
|
||||
* @param {string|null} sXML
|
||||
* @param {string} sXML
|
||||
* @param {string|null} sXMLFile
|
||||
* @param {string} [idMachine]
|
||||
* @param {string} [sStateFile]
|
||||
* @param {boolean} [fResolve] is true to resolve any "ref" attributes; default is false
|
||||
* @return {Array} where [0] contains the unparsed XML string data, and [1] contains a parsed XML object
|
||||
* @param {string|null|undefined} idMachine
|
||||
* @param {string|null|undefined} sStateFile
|
||||
* @param {boolean} fResolve is true to resolve any "ref" attributes; default is false
|
||||
* @param {function(string,Object)} done (string contains the unparsed XML string data, and Object contains a parsed XML object)
|
||||
*/
|
||||
function parseXML(sXML, sXMLFile, idMachine, sStateFile, fResolve)
|
||||
function parseXML(sXML, sXMLFile, idMachine, sStateFile, fResolve, done)
|
||||
{
|
||||
var xmlDoc = null;
|
||||
if (sXML) {
|
||||
if (fResolve) {
|
||||
sXML = resolveRefs(sXML);
|
||||
var buildXML = function(sXML, sError) {
|
||||
if (sError) {
|
||||
done(sError, null);
|
||||
return;
|
||||
}
|
||||
if (idMachine) {
|
||||
var sURL = sXMLFile;
|
||||
|
|
@ -120,13 +138,15 @@ function parseXML(sXML, sXMLFile, idMachine, sStateFile, fResolve)
|
|||
* The best I can do at this stage (assuming web.loadResource() didn't drop any error information on the floor)
|
||||
* is verify that the requested resource "looks like" valid XML (in other words, it begins with a "<").
|
||||
*/
|
||||
var xmlDoc = null;
|
||||
if (sXML.indexOf("<") === 0) {
|
||||
try {
|
||||
if (window.ActiveXObject || "ActiveXObject" in window) { // second test is required for IE11 on Windows 8.1
|
||||
/*
|
||||
* Another hack for MSIE, which fails to properly load XSL documents containing a <!DOCTYPE [...]> tag.
|
||||
*/
|
||||
if (!fResolve) {
|
||||
sXML = sXML.replace(/<!DOCTYPE(.|[\r\n])*\]>\s*/g, "");
|
||||
sXML = sXML.replace(/<!DOCTYPE(.|[\r\n])*]>\s*/g, "");
|
||||
}
|
||||
xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
|
||||
xmlDoc.async = false;
|
||||
|
|
@ -134,15 +154,28 @@ function parseXML(sXML, sXMLFile, idMachine, sStateFile, fResolve)
|
|||
} else {
|
||||
xmlDoc = (new window.DOMParser()).parseFromString(sXML, "text/xml");
|
||||
}
|
||||
} catch(e) {
|
||||
xmlDoc = null;
|
||||
sXML = e.message;
|
||||
}
|
||||
} else {
|
||||
throw new Error("unrecognized XML: " + (sXML.length > 255? sXML.substr(0, 255) + "..." : sXML));
|
||||
sXML = "unrecognized XML: " + (sXML.length > 255? sXML.substr(0, 255) + "..." : sXML);
|
||||
}
|
||||
done(sXML, xmlDoc);
|
||||
};
|
||||
if (sXML) {
|
||||
if (fResolve) {
|
||||
resolveXML(sXML, buildXML);
|
||||
return;
|
||||
}
|
||||
return [sXML, xmlDoc];
|
||||
buildXML(sXML, null);
|
||||
return;
|
||||
}
|
||||
done("no data" + (sXMLFile? " for file: " + sXMLFile : ""), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* resolvesRefs(sXML)
|
||||
* resolveXML(sXML, done)
|
||||
*
|
||||
* Replaces every tag with a "ref" attribute with the contents of the corresponding file.
|
||||
*
|
||||
|
|
@ -151,20 +184,21 @@ function parseXML(sXML, sXMLFile, idMachine, sStateFile, fResolve)
|
|||
* and 3) requiring the "ref" tag to be self-closing.
|
||||
*
|
||||
* @param {string} sXML
|
||||
* @returns {string} with all tags with "ref" attributes replaced with the referenced file instead
|
||||
* @param {function(string,(string|null))} done (the first string contains the resolved XML data, the second is for any error message)
|
||||
*/
|
||||
function resolveRefs(sXML)
|
||||
function resolveXML(sXML, done)
|
||||
{
|
||||
var matchRef, sError;
|
||||
var matchRef;
|
||||
var reRef = /<([a-z]+)\s+ref="(.*?)"(.*?)\/>/g;
|
||||
while ((matchRef = reRef.exec(sXML))) {
|
||||
|
||||
if ((matchRef = reRef.exec(sXML))) {
|
||||
|
||||
var sRefFile = matchRef[2];
|
||||
var response = web.loadResource(sRefFile);
|
||||
var sXMLRef = response[1];
|
||||
if (response[0] || !sXMLRef) {
|
||||
sError = "unable to resolve XML reference: " + matchRef[0] + " (" + response[0] + ")";
|
||||
Component.log(sError);
|
||||
throw new Error(sError);
|
||||
|
||||
var doneReadXML = function(sURLName, sXMLRef, nErrorCode) {
|
||||
if (nErrorCode || !sXMLRef) {
|
||||
done(sXML, "unable to resolve XML reference: " + matchRef[0] + " (" + nErrorCode + ")");
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* If there are additional attributes in the "referring" XML tag, we want to insert them
|
||||
|
|
@ -198,9 +232,8 @@ function resolveRefs(sXML)
|
|||
sXMLRef = sXMLRef.replace(aXMLRefTag[0], sXMLNewTag);
|
||||
}
|
||||
} else {
|
||||
sError = "missing <" + matchRef[1] + "> in " + sRefFile;
|
||||
Component.log(sError);
|
||||
throw new Error(sError);
|
||||
done(sXML, "missing <" + matchRef[1] + "> in " + sRefFile);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -216,9 +249,13 @@ function resolveRefs(sXML)
|
|||
|
||||
sXML = sXML.replace(matchRef[0], sXMLRef);
|
||||
|
||||
reRef.lastIndex = 0; // reset lastIndex, since we just modified the string that reRef is iterating over
|
||||
resolveXML(sXML, done);
|
||||
};
|
||||
|
||||
web.loadResource(sRefFile, fAsync, null, null, doneReadXML);
|
||||
return;
|
||||
}
|
||||
return sXML;
|
||||
done(sXML, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -226,17 +263,48 @@ function resolveRefs(sXML)
|
|||
*
|
||||
* This allows to you embed a machine on a web page, by transforming the machine XML into HTML.
|
||||
*
|
||||
* @param {string} sName is the app name (eg, "PCjs" or "C1Pjs")
|
||||
* @param {string} sVersion is the app version (eg, "1.12.1")
|
||||
* @param {string} sName is the app name (eg, "PCjs")
|
||||
* @param {string} sVersion is the app version (eg, "1.15.7")
|
||||
* @param {string} idElement
|
||||
* @param {string} sXMLFile
|
||||
* @param {string} [sXSLFile]
|
||||
* @param {string} sXSLFile
|
||||
* @param {string} [sStateFile]
|
||||
* @return {string} containing the complete XML string data, or an error if the XML could not be parsed
|
||||
* @return {boolean} true if successful, false if error
|
||||
*/
|
||||
function embedMachine(sName, sVersion, idElement, sXMLFile, sXSLFile, sStateFile)
|
||||
{
|
||||
var sXML = "", sError = "", eMachine = null;
|
||||
var eMachine, fSuccess = true;
|
||||
|
||||
cMachines++;
|
||||
|
||||
var doneMachine = function() {
|
||||
Component.assert(cMachines > 0);
|
||||
if (--cMachines == 0) {
|
||||
if (fAsync) web.enablePageEvents(true);
|
||||
}
|
||||
};
|
||||
|
||||
var displayError = function(sError) {
|
||||
Component.log(sError);
|
||||
if (eMachine) {
|
||||
/*
|
||||
* Our MarkOut module (in convertMDMachineLinks()) creates machine containers that look like this:
|
||||
*
|
||||
* <div id="' + sMachineID + '" class="machine-placeholder"><p>Embedded PC</p><p class="machine-warning"></p></div>
|
||||
*
|
||||
* with the "machine-warning" paragraph pre-populated with a warning message that the user will
|
||||
* see if nothing at all happens. But hopefully, in the normal case (and especially the error case),
|
||||
* *something* will have happened.
|
||||
*
|
||||
* Note that it is the HTMLOut module (in processMachines()) that ultimately decides which scripts to
|
||||
* include and then generates the embedPC() and/or embedC1P() calls.
|
||||
*/
|
||||
var aeError = Component.getElementsByClass(eMachine, "machine-warning");
|
||||
if (aeError[0]) aeError[0].innerHTML = "Error: " + str.escapeHTML(sError);
|
||||
}
|
||||
if (fSuccess) doneMachine();
|
||||
fSuccess = false;
|
||||
};
|
||||
|
||||
try {
|
||||
eMachine = window.document.getElementById(idElement);
|
||||
|
|
@ -249,12 +317,16 @@ function embedMachine(sName, sVersion, idElement, sXMLFile, sXSLFile, sStateFile
|
|||
sXSLFile = "/versions/" + sAppClass + "/" + sVersion + "/components.xsl";
|
||||
}
|
||||
}
|
||||
var aXML = (sXMLFile.substr(0, 1) == "<" ? parseXML(sXMLFile, null, idElement, sStateFile, false) : loadXML(sXMLFile, idElement, sStateFile, true));
|
||||
sXML = aXML[0];
|
||||
var xml = aXML[1];
|
||||
if (xml) {
|
||||
aXML = loadXML(sXSLFile);
|
||||
var xsl = aXML[1];
|
||||
var loadXSL = function(sXML, xml) {
|
||||
if (!xml) {
|
||||
displayError(sXML);
|
||||
return;
|
||||
}
|
||||
var transformXML = function(sXSL, xsl) {
|
||||
if (!xsl) {
|
||||
displayError(sXSL);
|
||||
return;
|
||||
}
|
||||
if (xsl) {
|
||||
/*
|
||||
* The <machine> template in components.xsl now generates a "machine div" that makes
|
||||
|
|
@ -273,8 +345,9 @@ function embedMachine(sName, sVersion, idElement, sXMLFile, sXSLFile, sStateFile
|
|||
var sFragment = xml['transformNode'](xsl);
|
||||
if (sFragment) {
|
||||
eMachine.outerHTML = sFragment;
|
||||
doneMachine();
|
||||
} else {
|
||||
Component.log(sError = "transformNodeToObject failed");
|
||||
displayError("transformNodeToObject failed");
|
||||
}
|
||||
}
|
||||
else if (window.document.implementation && window.document.implementation.createDocument) {
|
||||
|
|
@ -283,8 +356,9 @@ function embedMachine(sName, sVersion, idElement, sXMLFile, sXSLFile, sStateFile
|
|||
var eFragment = xsltProcessor['transformToFragment'](xml, window.document);
|
||||
if (eFragment) {
|
||||
eMachine.parentNode.replaceChild(eFragment, eMachine);
|
||||
doneMachine();
|
||||
} else {
|
||||
Component.log(sError = "transformToFragment failed");
|
||||
displayError("transformToFragment failed");
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
|
|
@ -292,41 +366,30 @@ function embedMachine(sName, sVersion, idElement, sXMLFile, sXSLFile, sStateFile
|
|||
* not aware of any browsers don't support one or both of the above XSLT transformation
|
||||
* methods, so treat this as a bug.
|
||||
*/
|
||||
Component.log(sError = "unable to transform XML: unsupported browser");
|
||||
displayError("unable to transform XML: unsupported browser");
|
||||
}
|
||||
} else {
|
||||
Component.log(sError = "failed to load XSL file: " + sXSLFile);
|
||||
displayError("failed to load XSL file: " + sXSLFile);
|
||||
}
|
||||
};
|
||||
if (xml) {
|
||||
loadXML(sXSLFile, null, null, false, transformXML);
|
||||
} else {
|
||||
displayError("failed to load XML file: " + sXMLFile);
|
||||
}
|
||||
};
|
||||
if (sXMLFile.substr(0, 1) != "<") {
|
||||
loadXML(sXMLFile, idElement, sStateFile, true, loadXSL);
|
||||
} else {
|
||||
parseXML(sXMLFile, null, idElement, sStateFile, false, loadXSL);
|
||||
}
|
||||
} else {
|
||||
Component.log(sError = "failed to load XML file: " + sXMLFile);
|
||||
}
|
||||
} else {
|
||||
Component.log(sError = "failed to find machine element: " + idElement);
|
||||
displayError("failed to find machine element: " + idElement);
|
||||
}
|
||||
} catch(e) {
|
||||
sError = e.message;
|
||||
displayError(e.message);
|
||||
}
|
||||
|
||||
if (sError && eMachine) {
|
||||
/*
|
||||
* Our MarkOut module (in convertMDMachineLinks()) creates machine containers that look like this:
|
||||
*
|
||||
* <div id="' + sMachineID + '" class="machine-placeholder"><p>Embedded PC</p><p class="machine-warning"></p></div>
|
||||
*
|
||||
* with the "machine-warning" paragraph pre-populated with a warning message that the user will
|
||||
* see if nothing at all happens. But hopefully, in the normal case (and especially the error case),
|
||||
* *something* will have happened.
|
||||
*
|
||||
* Note that it is the HTMLOut module (in processMachines()) that ultimately decides which scripts to
|
||||
* include and then generates the embedPC() and/or embedC1P() calls.
|
||||
*/
|
||||
var aeError = Component.getElementsByClass(eMachine, "machine-warning");
|
||||
if (aeError[0]) {
|
||||
aeError[0].innerHTML = "Error: " + str.escapeHTML(sError);
|
||||
}
|
||||
}
|
||||
|
||||
return sError || sXML;
|
||||
return fSuccess;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -334,8 +397,8 @@ function embedMachine(sName, sVersion, idElement, sXMLFile, sXSLFile, sStateFile
|
|||
*
|
||||
* @param {string} idElement
|
||||
* @param {string} sXMLFile
|
||||
* @param {string} [sXSLFile]
|
||||
* @return {string} XML string data or error message
|
||||
* @param {string} sXSLFile
|
||||
* @return {boolean} true if successful, false if error
|
||||
*/
|
||||
function embedC1P(idElement, sXMLFile, sXSLFile)
|
||||
{
|
||||
|
|
@ -347,9 +410,9 @@ function embedC1P(idElement, sXMLFile, sXSLFile)
|
|||
*
|
||||
* @param {string} idElement
|
||||
* @param {string} sXMLFile
|
||||
* @param {string} [sXSLFile]
|
||||
* @param {string} sXSLFile
|
||||
* @param {string} [sStateFile]
|
||||
* @return {string} XML string data or error message
|
||||
* @return {boolean} true if successful, false if error
|
||||
*/
|
||||
function embedPC(idElement, sXMLFile, sXSLFile, sStateFile)
|
||||
{
|
||||
|
|
@ -370,3 +433,5 @@ if (APPNAME == "C1Pjs") {
|
|||
|
||||
window['enableEvents'] = web.enablePageEvents;
|
||||
window['sendEvent'] = web.sendPageEvent;
|
||||
|
||||
if (fAsync) web.enablePageEvents(false);
|
||||
|
|
|
|||
|
|
@ -353,6 +353,7 @@ net.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNoti
|
|||
if (!sServerRoot) {
|
||||
sServerRoot = path.join(path.dirname(fs.realpathSync(__filename)), "../../../");
|
||||
}
|
||||
var sBaseName = str.getBaseName(sURL);
|
||||
var sFile = path.join(sServerRoot, sURL);
|
||||
if (fAsync) {
|
||||
fs.readFile(sFile, {encoding: "utf8"}, function(err, s) {
|
||||
|
|
@ -363,7 +364,13 @@ net.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNoti
|
|||
sResponse = s;
|
||||
nErrorCode = 0;
|
||||
}
|
||||
if (componentNotify && fnNotify) fnNotify.call(componentNotify, str.getBaseName(sURL), sResponse, nErrorCode, pNotify);
|
||||
if (fnNotify) {
|
||||
if (!componentNotify) {
|
||||
fnNotify(sBaseName, sResponse, nErrorCode, pNotify);
|
||||
} else {
|
||||
fnNotify.call(componentNotify, sBaseName, sResponse, nErrorCode, pNotify);
|
||||
}
|
||||
}
|
||||
});
|
||||
return [];
|
||||
} else {
|
||||
|
|
@ -376,7 +383,13 @@ net.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNoti
|
|||
*/
|
||||
console.log(err.message);
|
||||
}
|
||||
if (componentNotify && fnNotify) fnNotify.call(componentNotify, str.getBaseName(sURL), sResponse, nErrorCode, pNotify);
|
||||
if (fnNotify) {
|
||||
if (!componentNotify) {
|
||||
fnNotify(sBaseName, sResponse, nErrorCode, pNotify);
|
||||
} else {
|
||||
fnNotify.call(componentNotify, sBaseName, sResponse, nErrorCode, pNotify);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [nErrorCode, sResponse];
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ var web = {};
|
|||
*
|
||||
* @param {string} sURL
|
||||
* @param {boolean} [fAsync] is true for an asynchronous request
|
||||
* @param {Object|null} [data] for a POST request (default is a GET request)
|
||||
* @param {Object} [data] for a POST request (default is a GET request)
|
||||
* @param {Component} [componentNotify]
|
||||
* @param {function(...)} [fnNotify]
|
||||
* @param {number|string|null|Object|Array} [pNotify] optional fnNotify info parameter
|
||||
|
|
@ -174,13 +174,14 @@ web.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNoti
|
|||
xmlHTTP.onreadystatechange = function() {
|
||||
if (xmlHTTP.readyState === 4) {
|
||||
/*
|
||||
* The following line is recommended for WebKit, as a work-around to prevent the handler firing multiple
|
||||
* The following line was recommended for WebKit, as a work-around to prevent the handler firing multiple
|
||||
* times when debugging. Unfortunately, that's not the only XMLHttpRequest problem that occurs when
|
||||
* debugging, so I think the WebKit problem is deeper than that. When we have multiple XMLHttpRequests
|
||||
* pending, any debugging activity means most of them simply get dropped on floor, so what may actually be
|
||||
* happening are mis-notifications rather than redundant notifications.
|
||||
*/
|
||||
*
|
||||
xmlHTTP.onreadystatechange = undefined;
|
||||
*/
|
||||
sURLData = xmlHTTP.responseText;
|
||||
if (xmlHTTP.status == 200) {
|
||||
Component.log("xmlHTTP.onreadystatechange(" + sURL + "): returned " + sURLData.length + " bytes");
|
||||
|
|
@ -189,7 +190,13 @@ web.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNoti
|
|||
nErrorCode = xmlHTTP.status || -1;
|
||||
Component.log("xmlHTTP.onreadystatechange(" + sURL + "): error code " + nErrorCode);
|
||||
}
|
||||
if (componentNotify && fnNotify) fnNotify.call(componentNotify, sURLName, sURLData, nErrorCode, pNotify);
|
||||
if (fnNotify) {
|
||||
if (!componentNotify) {
|
||||
fnNotify(sURLName, sURLData, nErrorCode, pNotify);
|
||||
} else {
|
||||
fnNotify.call(componentNotify, sURLName, sURLData, nErrorCode, pNotify);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -219,7 +226,13 @@ web.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNoti
|
|||
nErrorCode = xmlHTTP.status || -1;
|
||||
Component.log("web.loadResource(" + sURL + "): error code " + nErrorCode);
|
||||
}
|
||||
if (componentNotify && fnNotify) fnNotify.call(componentNotify, sURLName, sURLData, nErrorCode, pNotify);
|
||||
if (fnNotify) {
|
||||
if (!componentNotify) {
|
||||
fnNotify(sURLName, sURLData, nErrorCode, pNotify);
|
||||
} else {
|
||||
fnNotify.call(componentNotify, sURLName, sURLData, nErrorCode, pNotify);
|
||||
}
|
||||
}
|
||||
response = [nErrorCode, sURLData];
|
||||
}
|
||||
return response;
|
||||
|
|
@ -326,6 +339,49 @@ web.promptUser = function(sPrompt, sDefault)
|
|||
return sResponse;
|
||||
};
|
||||
|
||||
/**
|
||||
* fLocalStorage
|
||||
*
|
||||
* true if localStorage support exists, is enabled, and works; "falsey" otherwise
|
||||
*
|
||||
* @type {boolean|undefined}
|
||||
*/
|
||||
web.fLocalStorage;
|
||||
|
||||
/**
|
||||
* hasLocalStorage
|
||||
*
|
||||
* true if localStorage support exists, is enabled, and works; false otherwise
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
web.hasLocalStorage = function() {
|
||||
if (web.fLocalStorage === undefined) {
|
||||
var f;
|
||||
var sTest = 'PCjs.localStorage';
|
||||
try {
|
||||
window.localStorage.setItem(sTest, sTest);
|
||||
f = (window.localStorage.getItem(sTest) === sTest);
|
||||
window.localStorage.removeItem(sTest);
|
||||
} catch(e) {
|
||||
web.logLocalStorageError(e);
|
||||
f = false;
|
||||
}
|
||||
web.fLocalStorage = f;
|
||||
}
|
||||
return web.fLocalStorage;
|
||||
};
|
||||
|
||||
/**
|
||||
* logLocalStorageError(e)
|
||||
*
|
||||
* @param {Error} e is an exception
|
||||
*/
|
||||
web.logLocalStorageError = function(e)
|
||||
{
|
||||
Component.log(e.message, "localStorage error");
|
||||
};
|
||||
|
||||
/**
|
||||
* getLocalStorageItem(sKey)
|
||||
*
|
||||
|
|
@ -337,14 +393,10 @@ web.promptUser = function(sPrompt, sDefault)
|
|||
web.getLocalStorageItem = function(sKey)
|
||||
{
|
||||
var sValue;
|
||||
if (window) {
|
||||
try {
|
||||
/*
|
||||
* A try/catch block is required, because if the user has disabled localStorage, some browsers feel the need
|
||||
* to throw an exception on any attempt to access it, even when using "typeof".
|
||||
*/
|
||||
sValue = window.localStorage.getItem(sKey);
|
||||
} catch(e) {}
|
||||
} catch(e) {
|
||||
web.logLocalStorageError(e);
|
||||
}
|
||||
return sValue;
|
||||
};
|
||||
|
|
@ -354,23 +406,51 @@ web.getLocalStorageItem = function(sKey)
|
|||
*
|
||||
* @param {string} sKey
|
||||
* @param {string} sValue
|
||||
* return {boolean} true if localStorage is available, false if not
|
||||
* @return {boolean} true if localStorage is available, false if not
|
||||
*/
|
||||
web.setLocalStorageItem = function(sKey, sValue)
|
||||
{
|
||||
if (window) {
|
||||
try {
|
||||
/*
|
||||
* A try/catch block is required, because if the user has disabled localStorage, some browsers feel the need
|
||||
* to throw an exception on any attempt to access it, even when using "typeof".
|
||||
*/
|
||||
window.localStorage.setItem(sKey, sValue);
|
||||
return true;
|
||||
} catch(e) {}
|
||||
} catch(e) {
|
||||
web.logLocalStorageError(e);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* removeLocalStorageItem(sKey)
|
||||
*
|
||||
* @param {string} sKey
|
||||
*/
|
||||
web.removeLocalStorageItem = function(sKey)
|
||||
{
|
||||
try {
|
||||
window.localStorage.removeItem(sKey);
|
||||
} catch(e) {
|
||||
web.logLocalStorageError(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* getLocalStorageKeys()
|
||||
*
|
||||
* @return {Array}
|
||||
*/
|
||||
web.getLocalStorageKeys = function()
|
||||
{
|
||||
var a = [];
|
||||
try {
|
||||
for (var i = 0, c = window.localStorage.length; i < c; i++) {
|
||||
a.push(window.localStorage.key(i));
|
||||
}
|
||||
} catch(e) {
|
||||
web.logLocalStorageError(e);
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
/**
|
||||
* reloadPage()
|
||||
*/
|
||||
|
|
@ -545,6 +625,7 @@ web.aPageEventHandlers = {
|
|||
'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;
|
||||
|
||||
/**
|
||||
|
|
@ -635,6 +716,11 @@ web.doPageEvent = function(afn)
|
|||
*/
|
||||
web.enablePageEvents = function(fEnable)
|
||||
{
|
||||
if (!web.fPageEventsEnabled && fEnable) {
|
||||
web.fPageEventsEnabled = true;
|
||||
if (web.fPageReady) web.sendPageEvent('init');
|
||||
return;
|
||||
}
|
||||
web.fPageEventsEnabled = fEnable;
|
||||
};
|
||||
|
||||
|
|
@ -652,7 +738,7 @@ web.sendPageEvent = function(sEvent)
|
|||
}
|
||||
};
|
||||
|
||||
web.onPageEvent('onload', function onPageLoad() { web.doPageEvent(web.aPageEventHandlers['init']); });
|
||||
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']); });
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue