test_utils.sub.js (8806B)
1 var TestUtils = (function() { 2 function randomString() { 3 var result = ""; 4 for (var i = 0; i < 5; i++) 5 result += String.fromCharCode(97 + Math.floor(Math.random() * 26)); 6 return result; 7 }; 8 9 /** 10 * Representation of one datatype. 11 * @typedef Datatype 12 * @type{object} 13 * @property{string} name Name of the datatype. 14 * @property{function():boolean} supported 15 * Whether this datatype is supported by this user agent. 16 * @method{function():Void} add A function to add an instance of the datatype. 17 * @method{function():boolean} isEmpty A function that tests whether 18 * the datatype's storage backend is empty. 19 */ 20 var Datatype; 21 22 var TestUtils = {}; 23 24 /** 25 * Various storage backends that are part of the 'storage' datatype. 26 * @param{Array.<Datatype>} 27 */ 28 TestUtils.STORAGE = [ 29 { 30 "name": "local storage", 31 "supported": function() { return !!window.localStorage; }, 32 "add": function() { 33 return new Promise(function(resolve, reject) { 34 localStorage.setItem(randomString(), randomString()); 35 resolve(); 36 }); 37 }, 38 "isEmpty": function() { 39 return new Promise(function(resolve, reject) { 40 resolve(!localStorage.length); 41 }); 42 } 43 }, 44 { 45 "name": "Indexed DB", 46 "supported": function() { return !!window.indexedDB; }, 47 "add": function() { 48 return new Promise(function(resolve, reject) { 49 var request = window.indexedDB.open("database"); 50 request.onupgradeneeded = function() { 51 request.result.createObjectStore("store"); 52 }; 53 request.onsuccess = function() { 54 request.result.close(); 55 resolve(); 56 } 57 }); 58 }, 59 "isEmpty": function() { 60 return new Promise(function(resolve, reject) { 61 var request = window.indexedDB.open("database"); 62 request.onsuccess = function() { 63 var database = request.result; 64 try { 65 var transaction = database.transaction(["store"]); 66 resolve(false); 67 } catch(error) { 68 // The database is empty. However, by testing that, we have also 69 // created it, which means that |onupgradeneeded| in the "add" 70 // method will not run the next time. Delete the database before 71 // reporting that it was empty. 72 var deletion = window.indexedDB.deleteDatabase("database"); 73 deletion.onsuccess = resolve.bind(this, true); 74 } finally { 75 database.close(); 76 } 77 }; 78 }); 79 } 80 }, 81 { 82 // TODO(@msramek): We should also test the PERSISTENT filesystem, however, 83 // that might require storage permissions. 84 "name": "filesystems", 85 "supported": function() { 86 return window.requestFileSystem || window.webkitRequestFileSystem; 87 }, 88 "add": function() { 89 return new Promise(function(resolve, reject) { 90 var onSuccess = function(fileSystem) { 91 fileSystem.root.getFile('file', {"create": true}, resolve, resolve); 92 } 93 var onFailure = resolve; 94 95 var requestFileSystem = 96 window.requestFileSystem || window.webkitRequestFileSystem; 97 requestFileSystem(window.TEMPORARY, 1 /* 1B */, 98 onSuccess, onFailure); 99 }); 100 }, 101 "isEmpty": function() { 102 return new Promise(function(resolve, reject) { 103 var onSuccess = function(fileSystem) { 104 fileSystem.root.getFile( 105 'file', {}, 106 resolve.bind(this, false) /* opened successfully */, 107 resolve.bind(this, true) /* failed to open */); 108 } 109 var onFailure = resolve.bind(this, true); 110 111 var requestFileSystem = 112 window.requestFileSystem || window.webkitRequestFileSystem; 113 requestFileSystem(window.TEMPORARY, 1 /* 1B */, 114 onSuccess, onFailure); 115 }); 116 } 117 }, 118 { 119 "name": "service workers", 120 "supported": function() { return !!navigator.serviceWorker; }, 121 "add": function() { 122 return navigator.serviceWorker.register( 123 "support/service_worker.js", 124 { scope: "support/page_using_service_worker.html"}); 125 }, 126 "isEmpty": function() { 127 return new Promise(function(resolve, reject) { 128 navigator.serviceWorker.getRegistrations() 129 .then(function(registrations) { 130 resolve(!registrations.length); 131 }); 132 }); 133 } 134 }, 135 { 136 "name": "Storage Buckets", 137 "supported": function() { return !!navigator.storageBuckets; }, 138 "add": function() { 139 return navigator.storageBuckets.open('inbox_bucket'); 140 }, 141 "isEmpty": function() { 142 return new Promise(async function(resolve, reject) { 143 var keys = await navigator.storageBuckets.keys(); 144 resolve(!keys.includes('inbox_bucket')); 145 }); 146 } 147 }, 148 ].filter(function(backend) { return backend.supported(); }); 149 150 /** 151 * All datatypes supported by Clear-Site-Data. 152 * @param{Array.<Datatype>} 153 */ 154 TestUtils.DATATYPES = [ 155 { 156 "name": "cookies", 157 "supported": function() { return typeof document.cookie == "string"; }, 158 "add": function() { 159 return new Promise(function(resolve, reject) { 160 document.cookie = randomString() + "=" + randomString(); 161 resolve(); 162 }); 163 }, 164 "isEmpty": function() { 165 return new Promise(function(resolve, reject) { 166 resolve(!document.cookie); 167 }); 168 } 169 }, 170 { 171 "name": "storage", 172 "supported": TestUtils.STORAGE[0].supported, 173 "add": TestUtils.STORAGE[0].add, 174 "isEmpty": TestUtils.STORAGE[0].isEmpty, 175 } 176 ].filter(function(datatype) { return datatype.supported(); }); 177 178 /** 179 * All possible combinations of datatypes. 180 * @property {Array.<Array.<Datatype>>} 181 */ 182 TestUtils.COMBINATIONS = (function() { 183 var combinations = []; 184 for (var mask = 0; mask < (1 << TestUtils.DATATYPES.length); mask++) { 185 var combination = []; 186 187 for (var datatype = 0; 188 datatype < TestUtils.DATATYPES.length; datatype++) { 189 if (mask & (1 << datatype)) 190 combination.push(TestUtils.DATATYPES[datatype]); 191 } 192 193 combinations.push(combination); 194 } 195 return combinations; 196 })(); 197 198 /** 199 * Populates |datatypes| by calling the "add" method on each of them, 200 * and verifies that they are nonempty. 201 * @param {Array.<Datatype>} datatypes to be populated. 202 * @private 203 */ 204 function populate(datatypes) { 205 return Promise.all(datatypes.map(function(datatype) { 206 return new Promise(function(resolve, reject) { 207 datatype.add().then(function() { 208 datatype.isEmpty().then(function(isEmpty) { 209 assert_false( 210 isEmpty, 211 datatype.name + 212 " has to be nonempty before the test starts."); 213 resolve(); 214 }); 215 }); 216 }); 217 })); 218 }; 219 220 /** 221 * Ensures that all datatypes are nonempty. Should be called in the test 222 * setup phase. 223 */ 224 TestUtils.populateDatatypes = populate.bind(this, TestUtils.DATATYPES); 225 226 /** 227 * Ensures that all backends of the "storage" datatype are nonempty. Should 228 * be called in the test setup phase. 229 */ 230 TestUtils.populateStorage = populate.bind(this, TestUtils.STORAGE); 231 232 /** 233 * Get the support server URL that returns a Clear-Site-Data header 234 * to clear |datatypes|. 235 * @param{Array.<Datatype>} datatypes The list of datatypes to be deleted. 236 * @return string The URL to be queried. 237 */ 238 TestUtils.getClearSiteDataUrl = function(datatypes) { 239 names = datatypes.map(function(e) { return e.name }); 240 return "support/echo-clear-site-data.py?" + names.join("&"); 241 } 242 243 /** 244 * @param{string} page_scheme Scheme of the page. "http" or "https". 245 * @param{string} resource_scheme Scheme of the resource. "http" or "https". 246 * @return The URL of a page that contains a resource requesting the deletion 247 * of storage. 248 */ 249 TestUtils.getPageWithResourceUrl = function(page_scheme, resource_scheme) { 250 if (page_scheme != "https" && page_scheme != "http") 251 throw "Unsupported scheme: " + page_scheme; 252 if (resource_scheme != "https" && resource_scheme != "http") 253 throw "Unsupported scheme: " + resource_scheme; 254 return page_scheme + "://{{domains[]}}:" + 255 (page_scheme == "https" ? {{ports[https][0]}} : {{ports[http][0]}}) + 256 "/clear-site-data/support/page_with_resource.sub.html?scheme=" + 257 resource_scheme; 258 } 259 260 return TestUtils; 261 })();