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:
Jeff Parsons 2014-10-28 17:12:24 -07:00 committed by jeffpar
commit e0487bccd8
9 changed files with 1397 additions and 1245 deletions

View file

@ -41,9 +41,24 @@ 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.
*
@ -51,10 +66,10 @@ if (typeof module !== 'undefined') {
* XSL document loaded by JavaScript's XSLT processor, that has prevented me from dynamically
* loading any XML machine file that uses the "ref" attribute to refer to and incorporate
* another XML document.
*
*
* 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]);
}
return parseXML(response[1], sXMLFile, idMachine, sStateFile, fResolve);
var doneLoadXML = function(sURLName, sXML, nErrorCode) {
if (nErrorCode) {
done(sXML, null);
return;
}
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;
@ -110,134 +128,184 @@ function parseXML(sXML, sXMLFile, idMachine, sStateFile, fResolve)
* If the resource we requested is not really an XML file (or the file didn't exist and the server simply returned
* a message like "Cannot GET /configs/pc/machines/5150/cga/64kb/donkey/index.xml"), we'd like to display a more
* meaningful message, because the XML DOM parsers will blithely return a document that contains nothing useful; eg:
*
*
* This page contains the following errors:error on line 1 at column 1:
* Document is empty Below is a rendering of the page up to the first error.
*
*
* Supposedly, the IE XML DOM parser will throw an exception, but I haven't tested that, and unless all other
* browsers do that, that's not helpful.
*
*
* 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) {
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, "");
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, "");
}
xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = false;
xmlDoc.loadXML(sXML);
} else {
xmlDoc = (new window.DOMParser()).parseFromString(sXML, "text/xml");
}
xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = false;
xmlDoc.loadXML(sXML);
} 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;
}
buildXML(sXML, null);
return;
}
return [sXML, xmlDoc];
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.
*
*
* TODO: Fix some of the limitations of this code, such as: 1) requiring the "ref" attribute
* to appear as the tag's first attribute, 2) requiring the "ref" attribute to be double-quoted,
* and 3) requiring the "ref" tag to be self-closing.
* 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);
}
/*
* If there are additional attributes in the "referring" XML tag, we want to insert them
* into the "referred" XML tag; attributes that don't exist in the referred tag should be
* appended, and attributes that DO exist should be overwritten.
*/
var sRefAttrs = matchRef[3];
if (sRefAttrs) {
var aXMLRefTag = sXMLRef.match(new RegExp("<" + matchRef[1] + "[^>]*>"));
if (aXMLRefTag) {
var sXMLNewTag = aXMLRefTag[0];
/*
* Iterate over all the attributes in the "referring" XML tag (sRefAttrs)
*/
var matchAttr;
var reAttr = /( [a-z]+=)(['"])(.*?)\2/g;
while ((matchAttr = reAttr.exec(sRefAttrs))) {
if (sXMLNewTag.indexOf(matchAttr[1]) < 0) {
/*
* This is the append case
*/
sXMLNewTag = sXMLNewTag.replace(">", matchAttr[0] + ">");
} else {
/*
* This is the overwrite case
*/
sXMLNewTag = sXMLNewTag.replace(new RegExp(matchAttr[1] + "(['\"])(.*?)\\1"), matchAttr[0]);
}
}
if (aXMLRefTag[0] != sXMLNewTag) {
sXMLRef = sXMLRef.replace(aXMLRefTag[0], sXMLNewTag);
}
} else {
sError = "missing <" + matchRef[1] + "> in " + sRefFile;
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
* into the "referred" XML tag; attributes that don't exist in the referred tag should be
* appended, and attributes that DO exist should be overwritten.
*/
var sRefAttrs = matchRef[3];
if (sRefAttrs) {
var aXMLRefTag = sXMLRef.match(new RegExp("<" + matchRef[1] + "[^>]*>"));
if (aXMLRefTag) {
var sXMLNewTag = aXMLRefTag[0];
/*
* Iterate over all the attributes in the "referring" XML tag (sRefAttrs)
*/
var matchAttr;
var reAttr = /( [a-z]+=)(['"])(.*?)\2/g;
while ((matchAttr = reAttr.exec(sRefAttrs))) {
if (sXMLNewTag.indexOf(matchAttr[1]) < 0) {
/*
* This is the append case
*/
sXMLNewTag = sXMLNewTag.replace(">", matchAttr[0] + ">");
} else {
/*
* This is the overwrite case
*/
sXMLNewTag = sXMLNewTag.replace(new RegExp(matchAttr[1] + "(['\"])(.*?)\\1"), matchAttr[0]);
}
}
if (aXMLRefTag[0] != sXMLNewTag) {
sXMLRef = sXMLRef.replace(aXMLRefTag[0], sXMLNewTag);
}
} else {
done(sXML, "missing <" + matchRef[1] + "> in " + sRefFile);
return;
}
}
}
/*
* Apparently when a Windows Azure server delivers one of my XML files, it may modify the first line:
*
* <?xml version="1.0" encoding="UTF-8"?>\n
*
* I didn't determine exactly what it was doing at this point (probably just changing the \n to \r\n),
* but in any case, relaxing the following replace() solved it.
*/
sXMLRef = sXMLRef.replace(/<\?xml[^>]*>[\r\n]*/, "");
/*
* Apparently when a Windows Azure server delivers one of my XML files, it may modify the first line:
*
* <?xml version="1.0" encoding="UTF-8"?>\n
*
* I didn't determine exactly what it was doing at this point (probably just changing the \n to \r\n),
* but in any case, relaxing the following replace() solved it.
*/
sXMLRef = sXMLRef.replace(/<\?xml[^>]*>[\r\n]*/, "");
sXML = sXML.replace(matchRef[0], sXMLRef);
reRef.lastIndex = 0; // reset lastIndex, since we just modified the string that reRef is iterating over
sXML = sXML.replace(matchRef[0], sXMLRef);
resolveXML(sXML, done);
};
web.loadResource(sRefFile, fAsync, null, null, doneReadXML);
return;
}
return sXML;
done(sXML, null);
}
/**
* embedMachine(sName, sVersion, idElement, sXMLFile, sXSLFile, sStateFile)
*
*
* 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);
if (eMachine) {
@ -249,84 +317,79 @@ 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];
if (xsl) {
/*
* The <machine> template in components.xsl now generates a "machine div" that makes
* the div we required the caller of embedMachine() to provide redundant, so instead
* of appending this fragment to the caller's node, we REPLACE the caller's node.
* This works only because because we ALSO inject the caller's "machine div" ID into
* the fragment's ID during parseXML().
*
* eMachine.innerHTML = sFragment;
*
* Also, if the transform function fails, make sure you're using the appropriate
* "components.xsl" and not a "machine.xsl", because the latter will not produce valid
* embeddable HTML (and is the most common cause of failure at this final stage).
*/
if (window.ActiveXObject || "ActiveXObject" in window) { // second test is required for IE11 on Windows 8.1
var sFragment = xml['transformNode'](xsl);
if (sFragment) {
eMachine.outerHTML = sFragment;
} else {
Component.log(sError = "transformNodeToObject failed");
}
var loadXSL = function(sXML, xml) {
if (!xml) {
displayError(sXML);
return;
}
var transformXML = function(sXSL, xsl) {
if (!xsl) {
displayError(sXSL);
return;
}
else if (window.document.implementation && window.document.implementation.createDocument) {
var xsltProcessor = new XSLTProcessor();
xsltProcessor['importStylesheet'](xsl);
var eFragment = xsltProcessor['transformToFragment'](xml, window.document);
if (eFragment) {
eMachine.parentNode.replaceChild(eFragment, eMachine);
if (xsl) {
/*
* The <machine> template in components.xsl now generates a "machine div" that makes
* the div we required the caller of embedMachine() to provide redundant, so instead
* of appending this fragment to the caller's node, we REPLACE the caller's node.
* This works only because because we ALSO inject the caller's "machine div" ID into
* the fragment's ID during parseXML().
*
* eMachine.innerHTML = sFragment;
*
* Also, if the transform function fails, make sure you're using the appropriate
* "components.xsl" and not a "machine.xsl", because the latter will not produce valid
* embeddable HTML (and is the most common cause of failure at this final stage).
*/
if (window.ActiveXObject || "ActiveXObject" in window) { // second test is required for IE11 on Windows 8.1
var sFragment = xml['transformNode'](xsl);
if (sFragment) {
eMachine.outerHTML = sFragment;
doneMachine();
} else {
displayError("transformNodeToObject failed");
}
}
else if (window.document.implementation && window.document.implementation.createDocument) {
var xsltProcessor = new XSLTProcessor();
xsltProcessor['importStylesheet'](xsl);
var eFragment = xsltProcessor['transformToFragment'](xml, window.document);
if (eFragment) {
eMachine.parentNode.replaceChild(eFragment, eMachine);
doneMachine();
} else {
displayError("transformToFragment failed");
}
} else {
Component.log(sError = "transformToFragment failed");
/*
* Perhaps I should have performed this test at the outset; on the other hand, I'm
* not aware of any browsers don't support one or both of the above XSLT transformation
* methods, so treat this as a bug.
*/
displayError("unable to transform XML: unsupported browser");
}
} else {
/*
* Perhaps I should have performed this test at the outset; on the other hand, I'm
* 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("failed to load XSL file: " + sXSLFile);
}
};
if (xml) {
loadXML(sXSLFile, null, null, false, transformXML);
} else {
Component.log(sError = "failed to load XSL file: " + sXSLFile);
displayError("failed to load XML file: " + sXMLFile);
}
};
if (sXMLFile.substr(0, 1) != "<") {
loadXML(sXMLFile, idElement, sStateFile, true, loadXSL);
} else {
Component.log(sError = "failed to load XML file: " + sXMLFile);
parseXML(sXMLFile, null, idElement, sStateFile, false, loadXSL);
}
} 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);

View file

@ -93,7 +93,7 @@ net.hasParm = function(sParm, sValue, req)
*
* Propagates any "special" query parameters (as listed in asPropagate) from the given
* request object (req) to the given URL (sURL).
*
*
* We do not modify an sURL that already contains a '?' OR that begins with a protocol
* (eg, http:, mailto:, etc), in order to keep this function simple, since it's only for
* debugging purposes anyway. I also considered blowing off any URLs with a '#' for the
@ -221,7 +221,7 @@ net.getStat = function(sURL, done)
* @param {string} sURL is the source file
* @param {string|null} sEncoding is the encoding to assume, if any
* @param {function(Error,number,(string|Buffer))} done receives an Error, an HTTP status code, and a Buffer (if any)
*
*
* TODO: Add support for FTP? HTTPS? Anything else?
*/
net.getFile = function(sURL, sEncoding, done)
@ -230,11 +230,11 @@ net.getFile = function(sURL, sEncoding, done)
* Buffer objects are a fixed size, so my choices are: 1) call getStat() first, hope it returns
* the true size, and then preallocate a buffer; or 2) create a new, larger buffer every time a new
* chunk arrives. The latter seems best.
*
*
* However, if an encoding is given, we'll simply concatenate all the data into a String and return
* that instead. Note that the incoming data is always a Buffer, but concatenation with a String
* performs an implied "toString()" on the Buffer.
*
*
* WARNING: Even when an encoding is provided, we don't make any attempt to verify that the incoming
* data matches that encoding.
*/
@ -312,7 +312,7 @@ net.downloadFile = function(sURL, sFile, done)
/*
* TODO: We should try to update the file's modification time to match the 'last-modified'
* response header value, if any.
*
*
* TODO: Decide what to do when res.statusCode is actually an error code (eg, 404), because
* in such cases, the file content will likely just be an HTML error page.
*/
@ -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,10 +383,16 @@ 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];
};
if (typeof module !== 'undefined') module.exports = net;
if (typeof module !== 'undefined') module.exports = net;

View file

@ -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) {}
try {
sValue = window.localStorage.getItem(sKey);
} 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) {}
try {
window.localStorage.setItem(sKey, sValue);
return true;
} 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']); });