MockAlertsService.js (6797B)
1 "use strict"; 2 3 /* exported MockAlertsService */ 4 5 function mockServicesChromeScript() { 6 /* eslint-env mozilla/chrome-script */ 7 8 const MOCK_ALERTS_CID = Components.ID( 9 "{48068bc2-40ab-4904-8afd-4cdfb3a385f3}" 10 ); 11 const SYSTEM_CID = Components.ID("{a0ccaaf8-09da-44d8-b250-9ac3e93c8117}"); 12 const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1"; 13 14 const { setTimeout } = ChromeUtils.importESModule( 15 "resource://gre/modules/Timer.sys.mjs" 16 ); 17 const registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); 18 19 let activeNotifications = Object.create(null); 20 21 let throwHistory = false; 22 let history = []; 23 24 const mockAlertsService = { 25 showAlert(alert, listener) { 26 activeNotifications[alert.name] = { 27 listener, 28 cookie: alert.cookie, 29 title: alert.title, 30 }; 31 32 // fake async alert show event 33 if (listener) { 34 setTimeout(() => { 35 if (this.mockFailure) { 36 listener.observe(null, "alertfinished", alert.cookie); 37 return; 38 } 39 40 listener.observe(null, "alertshow", alert.cookie); 41 if (this.autoClick) { 42 let subject; 43 if (typeof this.autoClick === "string") { 44 subject = alert.actions.filter( 45 ac => ac.action === this.autoClick 46 )[0]; 47 } 48 listener.observe(subject, "alertclickcallback", alert.cookie); 49 } 50 }, 100); 51 } 52 }, 53 54 closeAlert(name) { 55 let alertNotification = activeNotifications[name]; 56 if (alertNotification) { 57 if (alertNotification.listener) { 58 alertNotification.listener.observe( 59 null, 60 "alertfinished", 61 alertNotification.cookie 62 ); 63 } 64 delete activeNotifications[name]; 65 } 66 }, 67 68 getHistory() { 69 if (throwHistory) { 70 throw new Error("no history, sorry"); 71 } 72 return history; 73 }, 74 75 QueryInterface: ChromeUtils.generateQI(["nsIAlertsService"]), 76 77 createInstance(iid) { 78 return this.QueryInterface(iid); 79 }, 80 81 // Some existing mochitests expect the mock to click the notification. 82 // The state here is specific to each caller as register() uses 83 // SpecialPowers.loadChromeScript that ensures evaluating on each call 84 // without caching objects. 85 autoClick: false, 86 }; 87 88 registrar.registerFactory( 89 MOCK_ALERTS_CID, 90 "alerts service", 91 ALERTS_SERVICE_CONTRACT_ID, 92 mockAlertsService 93 ); 94 95 function clickNotifications(doClose) { 96 // Until we need to close a specific notification, just click them all. 97 for (let [name, notification] of Object.entries(activeNotifications)) { 98 let { listener, cookie } = notification; 99 listener.observe(null, "alertclickcallback", cookie); 100 if (doClose) { 101 mockAlertsService.closeAlert(name); 102 } 103 } 104 } 105 106 function closeAllNotifications() { 107 for (let alertName of Object.keys(activeNotifications)) { 108 mockAlertsService.closeAlert(alertName); 109 } 110 } 111 112 const { addMessageListener, sendAsyncMessage } = this; 113 114 addMessageListener("mock-alert-service:unregister", () => { 115 closeAllNotifications(); 116 activeNotifications = null; 117 registrar.unregisterFactory(MOCK_ALERTS_CID, mockAlertsService); 118 // Revive the system one 119 registrar.registerFactory(SYSTEM_CID, "", ALERTS_SERVICE_CONTRACT_ID, null); 120 sendAsyncMessage("mock-alert-service:unregistered"); 121 }); 122 123 addMessageListener( 124 "mock-alert-service:click-notifications", 125 clickNotifications 126 ); 127 128 addMessageListener( 129 "mock-alert-service:close-notifications", 130 closeAllNotifications 131 ); 132 133 addMessageListener("mock-alert-service:close-notification", alertName => 134 mockAlertsService.closeAlert(alertName) 135 ); 136 137 addMessageListener("mock-alert-service:enable-autoclick", action => { 138 mockAlertsService.autoClick = action || true; 139 }); 140 141 addMessageListener("mock-alert-service:mock-failure", action => { 142 mockAlertsService.mockFailure = action || true; 143 }); 144 145 addMessageListener("mock-alert-service:get-notification-ids", () => 146 Object.keys(activeNotifications) 147 ); 148 149 addMessageListener("mock-alert-service:set-history", value => { 150 history = value; 151 }); 152 153 addMessageListener("mock-alert-service:set-throw-history", value => { 154 throwHistory = value; 155 }); 156 157 sendAsyncMessage("mock-alert-service:registered"); 158 } 159 160 const MockAlertsService = { 161 async register() { 162 if (this._chromeScript) { 163 throw new Error("MockAlertsService already registered"); 164 } 165 this._chromeScript = SpecialPowers.loadChromeScript( 166 mockServicesChromeScript 167 ); 168 // Make sure every registration will unregister automatically at test end 169 SimpleTest.registerCleanupFunction(async () => { 170 await MockAlertsService.unregister(); 171 }); 172 await this._chromeScript.promiseOneMessage("mock-alert-service:registered"); 173 }, 174 async unregister() { 175 if (!this._chromeScript) { 176 throw new Error("MockAlertsService not registered"); 177 } 178 this._chromeScript.sendAsyncMessage("mock-alert-service:unregister"); 179 return this._chromeScript 180 .promiseOneMessage("mock-alert-service:unregistered") 181 .then(() => { 182 this._chromeScript.destroy(); 183 this._chromeScript = null; 184 }); 185 }, 186 async clickNotifications() { 187 // Most implementations of the nsIAlertsService automatically close upon click. 188 await this._chromeScript.sendQuery( 189 "mock-alert-service:click-notifications", 190 true 191 ); 192 }, 193 async clickNotificationsWithoutClose() { 194 // The implementation on macOS does not automatically close the notification. 195 await this._chromeScript.sendQuery( 196 "mock-alert-service:click-notifications", 197 false 198 ); 199 }, 200 async closeNotifications() { 201 await this._chromeScript.sendQuery( 202 "mock-alert-service:close-notifications" 203 ); 204 }, 205 async closeNotification(alertName) { 206 await this._chromeScript.sendQuery( 207 "mock-alert-service:close-notification", 208 alertName 209 ); 210 }, 211 async enableAutoClick(action) { 212 await this._chromeScript.sendQuery( 213 "mock-alert-service:enable-autoclick", 214 action 215 ); 216 }, 217 async mockFailure(action) { 218 await this._chromeScript.sendQuery( 219 "mock-alert-service:mock-failure", 220 action 221 ); 222 }, 223 async getNotificationIds() { 224 return await this._chromeScript.sendQuery( 225 "mock-alert-service:get-notification-ids" 226 ); 227 }, 228 async setHistory(ids) { 229 return await this._chromeScript.sendQuery( 230 "mock-alert-service:set-history", 231 ids 232 ); 233 }, 234 async setThrowHistory(throws) { 235 return await this._chromeScript.sendQuery( 236 "mock-alert-service:set-throw-history", 237 throws 238 ); 239 }, 240 };