xpcshell-head-parent-process.js (17976B)
1 /** 2 * Any copyright is dedicated to the Public Domain. 3 * http://creativecommons.org/publicdomain/zero/1.0/ 4 */ 5 6 // Tests using testGenerator are expected to define it themselves. 7 // Testing functions are expected to call testSteps and its type should either 8 // be GeneratorFunction or AsyncFunction 9 /* global testGenerator, testSteps:false */ 10 11 var { classes: Cc, interfaces: Ci, utils: Cu } = Components; 12 13 if (!("self" in this)) { 14 this.self = this; 15 } 16 17 var bufferCache = []; 18 19 function is(a, b, msg) { 20 Assert.equal(a, b, msg); 21 } 22 23 function ok(cond, msg) { 24 Assert.ok(!!cond, msg); 25 } 26 27 function isnot(a, b, msg) { 28 Assert.notEqual(a, b, msg); 29 } 30 31 function todo(condition) { 32 todo_check_true(condition); 33 } 34 35 function run_test() { 36 runTest(); 37 } 38 39 if (!this.runTest) { 40 this.runTest = function () { 41 if (SpecialPowers.isMainProcess()) { 42 // XPCShell does not get a profile by default. 43 do_get_profile(); 44 45 enableTesting(); 46 enableExperimental(); 47 } 48 49 // In order to support converting tests to using async functions from using 50 // generator functions, we detect async functions by checking the name of 51 // function's constructor. 52 Assert.ok( 53 typeof testSteps === "function", 54 "There should be a testSteps function" 55 ); 56 if (testSteps.constructor.name === "AsyncFunction") { 57 // Do run our existing cleanup function that would normally be called by 58 // the generator's call to finishTest(). 59 registerCleanupFunction(function () { 60 if (SpecialPowers.isMainProcess()) { 61 resetTesting(); 62 } 63 }); 64 65 add_task(testSteps); 66 67 // Since we defined run_test, we must invoke run_next_test() to start the 68 // async test. 69 run_next_test(); 70 } else { 71 Assert.ok( 72 testSteps.constructor.name === "GeneratorFunction", 73 "Unsupported function type" 74 ); 75 76 do_test_pending(); 77 testGenerator.next(); 78 } 79 }; 80 } 81 82 function finishTest() { 83 if (SpecialPowers.isMainProcess()) { 84 resetExperimental(); 85 resetTesting(); 86 } 87 88 SpecialPowers.removeFiles(); 89 90 executeSoon(function () { 91 do_test_finished(); 92 }); 93 } 94 95 function grabEventAndContinueHandler(event) { 96 testGenerator.next(event); 97 } 98 99 function continueToNextStep() { 100 executeSoon(function () { 101 testGenerator.next(); 102 }); 103 } 104 105 function errorHandler(event) { 106 try { 107 dump("indexedDB error: " + event.target.error.name); 108 } catch (e) { 109 dump("indexedDB error: " + e); 110 } 111 Assert.ok(false); 112 finishTest(); 113 } 114 115 function unexpectedSuccessHandler() { 116 Assert.ok(false); 117 finishTest(); 118 } 119 120 function expectedErrorHandler(name) { 121 return function (event) { 122 Assert.equal(event.type, "error"); 123 Assert.equal(event.target.error.name, name); 124 event.preventDefault(); 125 grabEventAndContinueHandler(event); 126 }; 127 } 128 129 function expectUncaughtException() { 130 // This is dummy for xpcshell test. 131 } 132 133 function ExpectError(name, preventDefault) { 134 this._name = name; 135 this._preventDefault = preventDefault; 136 } 137 ExpectError.prototype = { 138 handleEvent(event) { 139 Assert.equal(event.type, "error"); 140 Assert.equal(this._name, event.target.error.name); 141 if (this._preventDefault) { 142 event.preventDefault(); 143 event.stopPropagation(); 144 } 145 grabEventAndContinueHandler(event); 146 }, 147 }; 148 149 function continueToNextStepSync() { 150 testGenerator.next(); 151 } 152 153 // TODO compareKeys is duplicated in ../helpers.js, can we import that here? 154 // the same applies to many other functions in this file 155 // this duplication should be avoided (bug 1565986) 156 function compareKeys(k1, k2) { 157 let t = typeof k1; 158 if (t != typeof k2) { 159 return false; 160 } 161 162 if (t !== "object") { 163 return k1 === k2; 164 } 165 166 if (k1 instanceof Date) { 167 return k2 instanceof Date && k1.getTime() === k2.getTime(); 168 } 169 170 if (k1 instanceof Array) { 171 if (!(k2 instanceof Array) || k1.length != k2.length) { 172 return false; 173 } 174 175 for (let i = 0; i < k1.length; ++i) { 176 if (!compareKeys(k1[i], k2[i])) { 177 return false; 178 } 179 } 180 181 return true; 182 } 183 184 if (k1 instanceof ArrayBuffer) { 185 if (!(k2 instanceof ArrayBuffer)) { 186 return false; 187 } 188 189 function arrayBuffersAreEqual(a, b) { 190 if (a.byteLength != b.byteLength) { 191 return false; 192 } 193 let ui8b = new Uint8Array(b); 194 return new Uint8Array(a).every((val, i) => val === ui8b[i]); 195 } 196 197 return arrayBuffersAreEqual(k1, k2); 198 } 199 200 return false; 201 } 202 203 function addPermission() { 204 throw new Error("addPermission"); 205 } 206 207 function removePermission() { 208 throw new Error("removePermission"); 209 } 210 211 function allowIndexedDB() { 212 throw new Error("allowIndexedDB"); 213 } 214 215 function disallowIndexedDB() { 216 throw new Error("disallowIndexedDB"); 217 } 218 219 function enableExperimental() { 220 SpecialPowers.setBoolPref("dom.indexedDB.experimental", true); 221 } 222 223 function resetExperimental() { 224 SpecialPowers.clearUserPref("dom.indexedDB.experimental"); 225 } 226 227 function enableTesting() { 228 SpecialPowers.setBoolPref("dom.quotaManager.testing", true); 229 SpecialPowers.setBoolPref("dom.indexedDB.testing", true); 230 } 231 232 function resetTesting() { 233 SpecialPowers.clearUserPref("dom.indexedDB.testing"); 234 SpecialPowers.clearUserPref("dom.quotaManager.testing"); 235 } 236 237 function gc() { 238 Cu.forceGC(); 239 Cu.forceCC(); 240 } 241 242 function scheduleGC() { 243 SpecialPowers.exactGC(continueToNextStep); 244 } 245 246 function setTimeout(fun, timeout) { 247 let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 248 var event = { 249 notify() { 250 fun(); 251 }, 252 }; 253 timer.initWithCallback(event, timeout, Ci.nsITimer.TYPE_ONE_SHOT); 254 return timer; 255 } 256 257 function initStorage() { 258 return Services.qms.init(); 259 } 260 261 function initPersistentOrigin(principal) { 262 return Services.qms.initializePersistentOrigin(principal); 263 } 264 265 function resetOrClearAllDatabases(callback, clear) { 266 if (!SpecialPowers.isMainProcess()) { 267 throw new Error("clearAllDatabases not implemented for child processes!"); 268 } 269 270 let request; 271 272 if (clear) { 273 request = Services.qms.clear(); 274 } else { 275 request = Services.qms.reset(); 276 } 277 278 request.callback = callback; 279 280 return request; 281 } 282 283 function resetAllDatabases(callback) { 284 return resetOrClearAllDatabases(callback, false); 285 } 286 287 function clearAllDatabases(callback) { 288 return resetOrClearAllDatabases(callback, true); 289 } 290 291 function installPackagedProfile(packageName) { 292 let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile); 293 294 let currentDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile); 295 296 let packageFile = currentDir.clone(); 297 packageFile.append(packageName + ".zip"); 298 299 let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance( 300 Ci.nsIZipReader 301 ); 302 zipReader.open(packageFile); 303 304 let entryNames = []; 305 for (let entry of zipReader.findEntries(null)) { 306 if (entry != "create_db.html") { 307 entryNames.push(entry); 308 } 309 } 310 entryNames.sort(); 311 312 for (let entryName of entryNames) { 313 let zipentry = zipReader.getEntry(entryName); 314 315 let file = profileDir.clone(); 316 let split = entryName.split("/"); 317 for (let i = 0; i < split.length; i++) { 318 file.append(split[i]); 319 } 320 321 if (zipentry.isDirectory) { 322 file.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8)); 323 } else { 324 let istream = zipReader.getInputStream(entryName); 325 326 var ostream = Cc[ 327 "@mozilla.org/network/file-output-stream;1" 328 ].createInstance(Ci.nsIFileOutputStream); 329 ostream.init(file, -1, parseInt("0644", 8), 0); 330 331 let bostream = Cc[ 332 "@mozilla.org/network/buffered-output-stream;1" 333 ].createInstance(Ci.nsIBufferedOutputStream); 334 bostream.init(ostream, 32768); 335 336 bostream.writeFrom(istream, istream.available()); 337 338 istream.close(); 339 bostream.close(); 340 } 341 } 342 343 zipReader.close(); 344 } 345 346 function getChromeFilesDir() { 347 let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile); 348 349 let idbDir = profileDir.clone(); 350 idbDir.append("storage"); 351 idbDir.append("permanent"); 352 idbDir.append("chrome"); 353 idbDir.append("idb"); 354 355 let idbEntries = idbDir.directoryEntries; 356 while (idbEntries.hasMoreElements()) { 357 let file = idbEntries.nextFile; 358 if (file.isDirectory()) { 359 return file; 360 } 361 } 362 363 throw new Error("files directory doesn't exist!"); 364 } 365 366 function getView(size) { 367 let buffer = new ArrayBuffer(size); 368 let view = new Uint8Array(buffer); 369 is(buffer.byteLength, size, "Correct byte length"); 370 return view; 371 } 372 373 function getRandomView(size) { 374 let view = getView(size); 375 for (let i = 0; i < size; i++) { 376 view[i] = parseInt(Math.random() * 255); 377 } 378 return view; 379 } 380 381 function getBlob(str) { 382 return new Blob([str], { type: "type/text" }); 383 } 384 385 function getFile(name, type, str) { 386 return new File([str], name, { type }); 387 } 388 389 function isWasmSupported() { 390 let testingFunctions = Cu.getJSTestingFunctions(); 391 return testingFunctions.wasmIsSupported(); 392 } 393 394 function getWasmModule(binary) { 395 let module = new WebAssembly.Module(binary); 396 return module; 397 } 398 399 function compareBuffers(buffer1, buffer2) { 400 if (buffer1.byteLength != buffer2.byteLength) { 401 return false; 402 } 403 404 let view1 = buffer1 instanceof Uint8Array ? buffer1 : new Uint8Array(buffer1); 405 let view2 = buffer2 instanceof Uint8Array ? buffer2 : new Uint8Array(buffer2); 406 for (let i = 0; i < buffer1.byteLength; i++) { 407 if (view1[i] != view2[i]) { 408 return false; 409 } 410 } 411 return true; 412 } 413 414 function verifyBuffers(buffer1, buffer2) { 415 ok(compareBuffers(buffer1, buffer2), "Correct buffer data"); 416 } 417 418 function verifyBlob(blob1, blob2) { 419 is(Blob.isInstance(blob1), true, "Instance of nsIDOMBlob"); 420 is(File.isInstance(blob1), File.isInstance(blob2), "Instance of DOM File"); 421 is(blob1.size, blob2.size, "Correct size"); 422 is(blob1.type, blob2.type, "Correct type"); 423 if (File.isInstance(blob2)) { 424 is(blob1.name, blob2.name, "Correct name"); 425 } 426 427 let buffer1; 428 let buffer2; 429 430 for (let i = 0; i < bufferCache.length; i++) { 431 if (bufferCache[i].blob == blob2) { 432 buffer2 = bufferCache[i].buffer; 433 break; 434 } 435 } 436 437 if (!buffer2) { 438 let reader = new FileReader(); 439 reader.readAsArrayBuffer(blob2); 440 reader.onload = function (event) { 441 buffer2 = event.target.result; 442 bufferCache.push({ blob: blob2, buffer: buffer2 }); 443 if (buffer1) { 444 verifyBuffers(buffer1, buffer2); 445 testGenerator.next(); 446 } 447 }; 448 } 449 450 let reader = new FileReader(); 451 reader.readAsArrayBuffer(blob1); 452 reader.onload = function (event) { 453 buffer1 = event.target.result; 454 if (buffer2) { 455 verifyBuffers(buffer1, buffer2); 456 testGenerator.next(); 457 } 458 }; 459 } 460 461 function verifyView(view1, view2) { 462 is(view1.byteLength, view2.byteLength, "Correct byteLength"); 463 verifyBuffers(view1, view2); 464 } 465 466 function grabFileUsageAndContinueHandler(request) { 467 testGenerator.next(request.result.fileUsage); 468 } 469 470 function getCurrentUsage(usageHandler) { 471 let principal = Cc["@mozilla.org/systemprincipal;1"].createInstance( 472 Ci.nsIPrincipal 473 ); 474 Services.qms.getUsageForPrincipal(principal, usageHandler); 475 } 476 477 function setTemporaryStorageLimit(limit) { 478 const pref = "dom.quotaManager.temporaryStorage.fixedLimit"; 479 if (limit) { 480 info("Setting temporary storage limit to " + limit); 481 SpecialPowers.setIntPref(pref, limit); 482 } else { 483 info("Removing temporary storage limit"); 484 SpecialPowers.clearUserPref(pref); 485 } 486 } 487 488 function setDataThreshold(threshold) { 489 info("Setting data threshold to " + threshold); 490 SpecialPowers.setIntPref("dom.indexedDB.dataThreshold", threshold); 491 } 492 493 function resetDataThreshold() { 494 info("Clearing data threshold pref"); 495 SpecialPowers.clearUserPref("dom.indexedDB.dataThreshold"); 496 } 497 498 function setMaxStructuredCloneSize(aSize) { 499 info("Setting maximal structured clone size to " + aSize); 500 SpecialPowers.setIntPref("dom.indexedDB.maxStructuredCloneSize", aSize); 501 } 502 503 function setMaxSerializedMsgSize(aSize) { 504 info("Setting maximal size of a serialized message to " + aSize); 505 SpecialPowers.setIntPref("dom.indexedDB.maxSerializedMsgSize", aSize); 506 } 507 508 function enablePreprocessing() { 509 info("Setting preprocessing pref"); 510 SpecialPowers.setBoolPref("dom.indexedDB.preprocessing", true); 511 } 512 513 function resetPreprocessing() { 514 info("Clearing preprocessing pref"); 515 SpecialPowers.clearUserPref("dom.indexedDB.preprocessing"); 516 } 517 518 function getSystemPrincipal() { 519 return Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); 520 } 521 522 function getPrincipal(url) { 523 let uri = Services.io.newURI(url); 524 return Services.scriptSecurityManager.createContentPrincipal(uri, {}); 525 } 526 527 class RequestError extends Error { 528 constructor(resultCode, resultName) { 529 super(`Request failed (code: ${resultCode}, name: ${resultName})`); 530 this.name = "RequestError"; 531 this.resultCode = resultCode; 532 this.resultName = resultName; 533 } 534 } 535 536 async function requestFinished(request) { 537 await new Promise(function (resolve) { 538 request.callback = function () { 539 resolve(); 540 }; 541 }); 542 543 if (request.resultCode !== Cr.NS_OK) { 544 throw new RequestError(request.resultCode, request.resultName); 545 } 546 547 return request.result; 548 } 549 550 // TODO: Rename to openDBRequestSucceeded ? 551 function expectingSuccess(request) { 552 return new Promise(function (resolve, reject) { 553 request.onerror = function (event) { 554 ok(false, "indexedDB error, '" + event.target.error.name + "'"); 555 reject(event); 556 }; 557 request.onsuccess = function (event) { 558 resolve(event); 559 }; 560 request.onupgradeneeded = function (event) { 561 ok(false, "Got upgrade, but did not expect it!"); 562 reject(event); 563 }; 564 }); 565 } 566 567 // TODO: Rename to openDBRequestUpgradeNeeded ? 568 function expectingUpgrade(request) { 569 return new Promise(function (resolve, reject) { 570 request.onerror = function (event) { 571 ok(false, "indexedDB error, '" + event.target.error.name + "'"); 572 reject(event); 573 }; 574 request.onupgradeneeded = function (event) { 575 resolve(event); 576 }; 577 request.onsuccess = function (event) { 578 ok(false, "Got success, but did not expect it!"); 579 reject(event); 580 }; 581 }); 582 } 583 584 function requestSucceeded(request, optionalSyncSuccessCallback) { 585 return new Promise(function (resolve, reject) { 586 request.onerror = function (event) { 587 ok(false, "indexedDB error, '" + event.target.error.name + "'"); 588 reject(event); 589 }; 590 request.onsuccess = function (event) { 591 if (optionalSyncSuccessCallback) { 592 optionalSyncSuccessCallback(); 593 } 594 resolve(event); 595 }; 596 }); 597 } 598 599 // Given a "/"-delimited path relative to the profile directory, 600 // return an nsIFile representing the path. This does not test 601 // for the existence of the file or parent directories. 602 // It is safe even on Windows where the directory separator is not "/", 603 // but make sure you're not passing in a "\"-delimited path. 604 function getRelativeFile(relativePath) { 605 let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile); 606 607 let file = profileDir.clone(); 608 relativePath.split("/").forEach(function (component) { 609 file.append(component); 610 }); 611 612 return file; 613 } 614 615 const isInChaosMode = () => { 616 return !!parseInt(Services.env.get("MOZ_CHAOSMODE"), 16); 617 }; 618 619 var SpecialPowers = { 620 isMainProcess() { 621 return ( 622 Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT 623 ); 624 }, 625 notifyObservers(subject, topic, data) { 626 Services.obs.notifyObservers(subject, topic, data); 627 }, 628 notifyObserversInParentProcess(subject, topic, data) { 629 if (subject) { 630 throw new Error("Can't send subject to another process!"); 631 } 632 return this.notifyObservers(subject, topic, data); 633 }, 634 getBoolPref(prefName) { 635 return Services.prefs.getBoolPref(prefName); 636 }, 637 setBoolPref(prefName, value) { 638 Services.prefs.setBoolPref(prefName, value); 639 }, 640 setIntPref(prefName, value) { 641 Services.prefs.setIntPref(prefName, value); 642 }, 643 clearUserPref(prefName) { 644 Services.prefs.clearUserPref(prefName); 645 }, 646 // Copied (and slightly adjusted) from testing/specialpowers/api.js 647 exactGC(callback) { 648 let count = 0; 649 650 function doPreciseGCandCC() { 651 function scheduledGCCallback() { 652 Cu.forceCC(); 653 654 if (++count < 3) { 655 doPreciseGCandCC(); 656 } else { 657 callback(); 658 } 659 } 660 661 Cu.schedulePreciseGC(scheduledGCCallback); 662 } 663 664 doPreciseGCandCC(); 665 }, 666 667 get Cc() { 668 return Cc; 669 }, 670 671 get Ci() { 672 return Ci; 673 }, 674 675 get Cu() { 676 return Cu; 677 }, 678 679 // Based on SpecialPowersObserver.prototype.receiveMessage 680 createFiles(requests, callback) { 681 let filePaths = []; 682 if (!this._createdFiles) { 683 this._createdFiles = []; 684 } 685 let createdFiles = this._createdFiles; 686 let promises = []; 687 requests.forEach(function (request) { 688 const filePerms = 0o666; 689 let testFile = Services.dirsvc.get("ProfD", Ci.nsIFile); 690 if (request.name) { 691 testFile.append(request.name); 692 } else { 693 testFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, filePerms); 694 } 695 let outStream = Cc[ 696 "@mozilla.org/network/file-output-stream;1" 697 ].createInstance(Ci.nsIFileOutputStream); 698 outStream.init( 699 testFile, 700 0x02 | 0x08 | 0x20, // PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE 701 filePerms, 702 0 703 ); 704 if (request.data) { 705 outStream.write(request.data, request.data.length); 706 outStream.close(); 707 } 708 promises.push( 709 File.createFromFileName(testFile.path, request.options).then( 710 function (file) { 711 filePaths.push(file); 712 } 713 ) 714 ); 715 createdFiles.push(testFile); 716 }); 717 718 Promise.all(promises).then(function () { 719 setTimeout(function () { 720 callback(filePaths); 721 }, 0); 722 }); 723 }, 724 725 removeFiles() { 726 if (this._createdFiles) { 727 this._createdFiles.forEach(function (testFile) { 728 try { 729 testFile.remove(false); 730 } catch (e) {} 731 }); 732 this._createdFiles = null; 733 } 734 }, 735 };