ExtensionTestUtils.js (5316B)
1 const { ExtensionTestCommon } = SpecialPowers.ChromeUtils.importESModule( 2 "resource://testing-common/ExtensionTestCommon.sys.mjs" 3 ); 4 5 var ExtensionTestUtils = { 6 // Shortcut to more easily access WebExtensionPolicy.backgroundServiceWorkerEnabled 7 // from mochitest-plain tests. 8 getBackgroundServiceWorkerEnabled() { 9 return ExtensionTestCommon.getBackgroundServiceWorkerEnabled(); 10 }, 11 12 // A test helper used to check if the pref "extension.backgroundServiceWorker.forceInTestExtension" 13 // is set to true. 14 isInBackgroundServiceWorkerTests() { 15 return ExtensionTestCommon.isInBackgroundServiceWorkerTests(); 16 }, 17 18 get testAssertions() { 19 return ExtensionTestCommon.testAssertions; 20 }, 21 }; 22 23 ExtensionTestUtils.loadExtension = function (ext) { 24 // Cleanup functions need to be registered differently depending on 25 // whether we're in browser chrome or plain mochitests. 26 var registerCleanup; 27 /* global registerCleanupFunction */ 28 if (typeof registerCleanupFunction != "undefined") { 29 registerCleanup = registerCleanupFunction; 30 } else { 31 registerCleanup = SimpleTest.registerCleanupFunction.bind(SimpleTest); 32 } 33 34 var testResolve; 35 var testDone = new Promise(resolve => { 36 testResolve = resolve; 37 }); 38 39 var messageHandler = new Map(); 40 var messageAwaiter = new Map(); 41 42 var messageQueue = new Set(); 43 44 registerCleanup(() => { 45 if (messageQueue.size) { 46 let names = Array.from(messageQueue, ([msg]) => msg); 47 SimpleTest.is(JSON.stringify(names), "[]", "message queue is empty"); 48 } 49 if (messageAwaiter.size) { 50 let names = Array.from(messageAwaiter.keys()); 51 SimpleTest.is( 52 JSON.stringify(names), 53 "[]", 54 "no tasks awaiting on messages" 55 ); 56 } 57 }); 58 59 function checkMessages() { 60 for (let message of messageQueue) { 61 let [msg, ...args] = message; 62 63 let listener = messageAwaiter.get(msg); 64 if (listener) { 65 messageQueue.delete(message); 66 messageAwaiter.delete(msg); 67 68 listener.resolve(...args); 69 return; 70 } 71 } 72 } 73 74 function checkDuplicateListeners(msg) { 75 if (messageHandler.has(msg) || messageAwaiter.has(msg)) { 76 throw new Error("only one message handler allowed"); 77 } 78 } 79 80 function testHandler(kind, pass, msg, ...args) { 81 if (kind == "test-eq") { 82 let [expected, actual] = args; 83 SimpleTest.ok(pass, `${msg} - Expected: ${expected}, Actual: ${actual}`); 84 } else if (kind == "test-log") { 85 SimpleTest.info(msg); 86 } else if (kind == "test-result") { 87 SimpleTest.ok(pass, msg); 88 } 89 } 90 91 var handler = { 92 async testResult(kind, pass, msg, ...args) { 93 if (kind == "test-done") { 94 SimpleTest.ok(pass, msg); 95 await testResolve(msg); 96 } 97 testHandler(kind, pass, msg, ...args); 98 }, 99 100 testMessage(msg, ...args) { 101 var msgHandler = messageHandler.get(msg); 102 if (msgHandler) { 103 msgHandler(...args); 104 } else { 105 messageQueue.add([msg, ...args]); 106 checkMessages(); 107 } 108 }, 109 }; 110 111 // Mimic serialization of functions as done in `Extension.generateXPI` and 112 // `Extension.generateZipFile` because functions are dropped when `ext` object 113 // is sent to the main process via the message manager. 114 ext = Object.assign({}, ext); 115 if (ext.files) { 116 ext.files = Object.assign({}, ext.files); 117 for (let filename of Object.keys(ext.files)) { 118 let file = ext.files[filename]; 119 if (typeof file === "function" || Array.isArray(file)) { 120 ext.files[filename] = ExtensionTestCommon.serializeScript(file); 121 } 122 } 123 } 124 if ("background" in ext) { 125 ext.background = ExtensionTestCommon.serializeScript(ext.background); 126 } 127 128 var extension = SpecialPowers.loadExtension(ext, handler); 129 130 registerCleanup(async () => { 131 if (extension.state == "pending" || extension.state == "running") { 132 SimpleTest.ok(false, "Extension left running at test shutdown"); 133 await extension.unload(); 134 } else if (extension.state == "unloading") { 135 SimpleTest.ok(false, "Extension not fully unloaded at test shutdown"); 136 } 137 }); 138 139 extension.awaitMessage = msg => { 140 return new Promise(resolve => { 141 checkDuplicateListeners(msg); 142 143 messageAwaiter.set(msg, { resolve }); 144 checkMessages(); 145 }); 146 }; 147 148 extension.onMessage = (msg, callback) => { 149 checkDuplicateListeners(msg); 150 messageHandler.set(msg, callback); 151 }; 152 153 extension.awaitFinish = msg => { 154 return testDone.then(actual => { 155 if (msg) { 156 SimpleTest.is(actual, msg, "test result correct"); 157 } 158 return actual; 159 }); 160 }; 161 162 SimpleTest.info(`Extension loaded`); 163 return extension; 164 }; 165 166 ExtensionTestUtils.failOnSchemaWarnings = (warningsAsErrors = true) => { 167 let prefName = "extensions.webextensions.warnings-as-errors"; 168 let prefPromise = SpecialPowers.setBoolPref(prefName, warningsAsErrors); 169 if (!warningsAsErrors) { 170 let registerCleanup; 171 if (typeof registerCleanupFunction != "undefined") { 172 registerCleanup = registerCleanupFunction; 173 } else { 174 registerCleanup = SimpleTest.registerCleanupFunction.bind(SimpleTest); 175 } 176 registerCleanup(() => SpecialPowers.setBoolPref(prefName, true)); 177 } 178 // In mochitests, setBoolPref is async. 179 return prefPromise.then(() => {}); 180 };