michael@0: var gBasePath = "tests/dom/apps/tests/"; michael@0: var gAppTemplatePath = "tests/dom/apps/tests/file_app.template.html"; michael@0: var gAppcacheTemplatePath = "tests/dom/apps/tests/file_cached_app.template.appcache"; michael@0: var gDefaultIcon = "default_icon"; michael@0: michael@0: function makeResource(templatePath, version, apptype) { michael@0: let icon = getState('icon') || gDefaultIcon; michael@0: var res = readTemplate(templatePath).replace(/VERSIONTOKEN/g, version) michael@0: .replace(/APPTYPETOKEN/g, apptype) michael@0: .replace(/ICONTOKEN/g, icon); michael@0: michael@0: // Hack - This is necessary to make the tests pass, but hbambas says it michael@0: // shouldn't be necessary. Comment it out and watch the tests fail. michael@0: if (templatePath == gAppTemplatePath && apptype == 'cached') { michael@0: res = res.replace('', ''); michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: function handleRequest(request, response) { michael@0: var query = getQuery(request); michael@0: michael@0: // If this is a version update, update state and return. michael@0: if ("setVersion" in query) { michael@0: setState('version', query.setVersion); michael@0: response.setHeader("Content-Type", "text/html", false); michael@0: response.setHeader("Access-Control-Allow-Origin", "*", false); michael@0: response.write('OK'); michael@0: return; michael@0: } michael@0: michael@0: if ("setIcon" in query) { michael@0: let icon = query.setIcon; michael@0: if (icon === 'DEFAULT') { michael@0: icon = null; michael@0: } michael@0: michael@0: setState('icon', icon); michael@0: michael@0: response.setHeader("Content-Type", "text/html", false); michael@0: response.setHeader("Access-Control-Allow-Origin", "*", false); michael@0: response.write('OK'); michael@0: return; michael@0: } michael@0: michael@0: // Get the app type. michael@0: var apptype = query.apptype; michael@0: if (apptype != 'hosted' && apptype != 'cached') michael@0: throw "Invalid app type: " + apptype; michael@0: michael@0: // Get the version from server state and handle the etag. michael@0: var version = Number(getState('version')); michael@0: var etag = getEtag(request, version); michael@0: dump("Server Etag: " + etag + "\n"); michael@0: michael@0: if (etagMatches(request, etag)) { michael@0: dump("Etags Match. Sending 304\n"); michael@0: response.setStatusLine(request.httpVersion, "304", "Not Modified"); michael@0: return; michael@0: } michael@0: michael@0: response.setHeader("Etag", etag, false); michael@0: if (request.hasHeader("If-None-Match")) michael@0: dump("Client Etag: " + request.getHeader("If-None-Match") + "\n"); michael@0: else michael@0: dump("No Client Etag\n"); michael@0: michael@0: // Check if we're generating a webapp manifest. michael@0: if ('getmanifest' in query) { michael@0: var template = gBasePath + 'file_' + apptype + '_app.template.webapp'; michael@0: response.setHeader("Content-Type", "application/x-web-app-manifest+json", false); michael@0: response.write(makeResource(template, version, apptype)); michael@0: return; michael@0: } michael@0: michael@0: // If apptype==cached, we might be generating the appcache manifest. michael@0: // michael@0: // NB: Among other reasons, we use the same sjs file here so that the version michael@0: // state is shared. michael@0: if (apptype == 'cached' && 'getappcache' in query) { michael@0: response.setHeader("Content-Type", "text/cache-manifest", false); michael@0: response.write(makeResource(gAppcacheTemplatePath, version, apptype)); michael@0: return; michael@0: } michael@0: michael@0: // Generate the app. michael@0: response.setHeader("Content-Type", "text/html", false); michael@0: response.write(makeResource(gAppTemplatePath, version, apptype)); michael@0: } michael@0: michael@0: function getEtag(request, version) { michael@0: return request.queryString.replace(/&/g, '-').replace(/=/g, '-') + '-' + version; michael@0: } michael@0: michael@0: function etagMatches(request, etag) { michael@0: return request.hasHeader("If-None-Match") && request.getHeader("If-None-Match") == etag; michael@0: } michael@0: michael@0: function getQuery(request) { michael@0: var query = {}; michael@0: request.queryString.split('&').forEach(function (val) { michael@0: var [name, value] = val.split('='); michael@0: query[name] = unescape(value); michael@0: }); michael@0: return query; michael@0: } michael@0: michael@0: // Copy-pasted incantations. There ought to be a better way to synchronously read michael@0: // a file into a string, but I guess we're trying to discourage that. michael@0: function readTemplate(path) { michael@0: var file = Components.classes["@mozilla.org/file/directory_service;1"]. michael@0: getService(Components.interfaces.nsIProperties). michael@0: get("CurWorkD", Components.interfaces.nsILocalFile); michael@0: var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. michael@0: createInstance(Components.interfaces.nsIFileInputStream); michael@0: var cis = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. michael@0: createInstance(Components.interfaces.nsIConverterInputStream); michael@0: var split = path.split("/"); michael@0: for(var i = 0; i < split.length; ++i) { michael@0: file.append(split[i]); michael@0: } michael@0: fis.init(file, -1, -1, false); michael@0: cis.init(fis, "UTF-8", 0, 0); michael@0: michael@0: var data = ""; michael@0: let (str = {}) { michael@0: let read = 0; michael@0: do { michael@0: read = cis.readString(0xffffffff, str); // read as much as we can and put it in str.value michael@0: data += str.value; michael@0: } while (read != 0); michael@0: } michael@0: cis.close(); michael@0: return data; michael@0: }