1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/apps/tests/file_app.sjs Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,136 @@ 1.4 +var gBasePath = "tests/dom/apps/tests/"; 1.5 +var gAppTemplatePath = "tests/dom/apps/tests/file_app.template.html"; 1.6 +var gAppcacheTemplatePath = "tests/dom/apps/tests/file_cached_app.template.appcache"; 1.7 +var gDefaultIcon = "default_icon"; 1.8 + 1.9 +function makeResource(templatePath, version, apptype) { 1.10 + let icon = getState('icon') || gDefaultIcon; 1.11 + var res = readTemplate(templatePath).replace(/VERSIONTOKEN/g, version) 1.12 + .replace(/APPTYPETOKEN/g, apptype) 1.13 + .replace(/ICONTOKEN/g, icon); 1.14 + 1.15 + // Hack - This is necessary to make the tests pass, but hbambas says it 1.16 + // shouldn't be necessary. Comment it out and watch the tests fail. 1.17 + if (templatePath == gAppTemplatePath && apptype == 'cached') { 1.18 + res = res.replace('<html>', '<html manifest="file_app.sjs?apptype=cached&getappcache=true">'); 1.19 + } 1.20 + 1.21 + return res; 1.22 +} 1.23 + 1.24 +function handleRequest(request, response) { 1.25 + var query = getQuery(request); 1.26 + 1.27 + // If this is a version update, update state and return. 1.28 + if ("setVersion" in query) { 1.29 + setState('version', query.setVersion); 1.30 + response.setHeader("Content-Type", "text/html", false); 1.31 + response.setHeader("Access-Control-Allow-Origin", "*", false); 1.32 + response.write('OK'); 1.33 + return; 1.34 + } 1.35 + 1.36 + if ("setIcon" in query) { 1.37 + let icon = query.setIcon; 1.38 + if (icon === 'DEFAULT') { 1.39 + icon = null; 1.40 + } 1.41 + 1.42 + setState('icon', icon); 1.43 + 1.44 + response.setHeader("Content-Type", "text/html", false); 1.45 + response.setHeader("Access-Control-Allow-Origin", "*", false); 1.46 + response.write('OK'); 1.47 + return; 1.48 + } 1.49 + 1.50 + // Get the app type. 1.51 + var apptype = query.apptype; 1.52 + if (apptype != 'hosted' && apptype != 'cached') 1.53 + throw "Invalid app type: " + apptype; 1.54 + 1.55 + // Get the version from server state and handle the etag. 1.56 + var version = Number(getState('version')); 1.57 + var etag = getEtag(request, version); 1.58 + dump("Server Etag: " + etag + "\n"); 1.59 + 1.60 + if (etagMatches(request, etag)) { 1.61 + dump("Etags Match. Sending 304\n"); 1.62 + response.setStatusLine(request.httpVersion, "304", "Not Modified"); 1.63 + return; 1.64 + } 1.65 + 1.66 + response.setHeader("Etag", etag, false); 1.67 + if (request.hasHeader("If-None-Match")) 1.68 + dump("Client Etag: " + request.getHeader("If-None-Match") + "\n"); 1.69 + else 1.70 + dump("No Client Etag\n"); 1.71 + 1.72 + // Check if we're generating a webapp manifest. 1.73 + if ('getmanifest' in query) { 1.74 + var template = gBasePath + 'file_' + apptype + '_app.template.webapp'; 1.75 + response.setHeader("Content-Type", "application/x-web-app-manifest+json", false); 1.76 + response.write(makeResource(template, version, apptype)); 1.77 + return; 1.78 + } 1.79 + 1.80 + // If apptype==cached, we might be generating the appcache manifest. 1.81 + // 1.82 + // NB: Among other reasons, we use the same sjs file here so that the version 1.83 + // state is shared. 1.84 + if (apptype == 'cached' && 'getappcache' in query) { 1.85 + response.setHeader("Content-Type", "text/cache-manifest", false); 1.86 + response.write(makeResource(gAppcacheTemplatePath, version, apptype)); 1.87 + return; 1.88 + } 1.89 + 1.90 + // Generate the app. 1.91 + response.setHeader("Content-Type", "text/html", false); 1.92 + response.write(makeResource(gAppTemplatePath, version, apptype)); 1.93 +} 1.94 + 1.95 +function getEtag(request, version) { 1.96 + return request.queryString.replace(/&/g, '-').replace(/=/g, '-') + '-' + version; 1.97 +} 1.98 + 1.99 +function etagMatches(request, etag) { 1.100 + return request.hasHeader("If-None-Match") && request.getHeader("If-None-Match") == etag; 1.101 +} 1.102 + 1.103 +function getQuery(request) { 1.104 + var query = {}; 1.105 + request.queryString.split('&').forEach(function (val) { 1.106 + var [name, value] = val.split('='); 1.107 + query[name] = unescape(value); 1.108 + }); 1.109 + return query; 1.110 +} 1.111 + 1.112 +// Copy-pasted incantations. There ought to be a better way to synchronously read 1.113 +// a file into a string, but I guess we're trying to discourage that. 1.114 +function readTemplate(path) { 1.115 + var file = Components.classes["@mozilla.org/file/directory_service;1"]. 1.116 + getService(Components.interfaces.nsIProperties). 1.117 + get("CurWorkD", Components.interfaces.nsILocalFile); 1.118 + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. 1.119 + createInstance(Components.interfaces.nsIFileInputStream); 1.120 + var cis = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. 1.121 + createInstance(Components.interfaces.nsIConverterInputStream); 1.122 + var split = path.split("/"); 1.123 + for(var i = 0; i < split.length; ++i) { 1.124 + file.append(split[i]); 1.125 + } 1.126 + fis.init(file, -1, -1, false); 1.127 + cis.init(fis, "UTF-8", 0, 0); 1.128 + 1.129 + var data = ""; 1.130 + let (str = {}) { 1.131 + let read = 0; 1.132 + do { 1.133 + read = cis.readString(0xffffffff, str); // read as much as we can and put it in str.value 1.134 + data += str.value; 1.135 + } while (read != 0); 1.136 + } 1.137 + cis.close(); 1.138 + return data; 1.139 +}