AboutLoginsChild.sys.mjs (9077B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { LoginHelper } from "resource://gre/modules/LoginHelper.sys.mjs"; 6 7 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; 8 9 const lazy = {}; 10 11 XPCOMUtils.defineLazyServiceGetter( 12 lazy, 13 "ClipboardHelper", 14 "@mozilla.org/widget/clipboardhelper;1", 15 Ci.nsIClipboardHelper 16 ); 17 18 const TELEMETRY_MIN_MS_BETWEEN_OPEN_MANAGEMENT = 5000; 19 20 let gLastOpenManagementBrowserId = null; 21 let gLastOpenManagementEventTime = Number.NEGATIVE_INFINITY; 22 let gPrimaryPasswordPromise; 23 24 function recordTelemetryEvent(event) { 25 try { 26 let { name, extra = {}, value = null } = event; 27 if (value) { 28 extra.value = value; 29 } 30 Glean.pwmgr[name].record(extra); 31 } catch (ex) { 32 console.error("AboutLoginsChild: error recording telemetry event:", ex); 33 } 34 } 35 36 export class AboutLoginsChild extends JSWindowActorChild { 37 handleEvent(event) { 38 switch (event.type) { 39 case "AboutLoginsInit": { 40 this.#aboutLoginsInit(); 41 break; 42 } 43 case "AboutLoginsImportReportInit": { 44 this.#aboutLoginsImportReportInit(); 45 break; 46 } 47 case "AboutLoginsCopyLoginDetail": { 48 this.#aboutLoginsCopyLoginDetail(event.detail); 49 break; 50 } 51 case "AboutLoginsCreateLogin": { 52 this.#aboutLoginsCreateLogin(event.detail); 53 break; 54 } 55 case "AboutLoginsDeleteLogin": { 56 this.#aboutLoginsDeleteLogin(event.detail); 57 break; 58 } 59 case "AboutLoginsExportPasswords": { 60 this.#aboutLoginsExportPasswords(); 61 break; 62 } 63 case "AboutLoginsGetHelp": { 64 this.#aboutLoginsGetHelp(); 65 break; 66 } 67 case "AboutLoginsImportFromBrowser": { 68 this.#aboutLoginsImportFromBrowser(); 69 break; 70 } 71 case "AboutLoginsImportFromFile": { 72 this.#aboutLoginsImportFromFile(); 73 break; 74 } 75 case "AboutLoginsOpenPreferences": { 76 this.#aboutLoginsOpenPreferences(); 77 break; 78 } 79 case "AboutLoginsRecordTelemetryEvent": { 80 this.#aboutLoginsRecordTelemetryEvent(event); 81 break; 82 } 83 case "AboutLoginsRemoveAllLogins": { 84 this.#aboutLoginsRemoveAllLogins(); 85 break; 86 } 87 case "AboutLoginsSortChanged": { 88 this.#aboutLoginsSortChanged(event.detail); 89 break; 90 } 91 case "AboutLoginsSyncEnable": { 92 this.#aboutLoginsSyncEnable(); 93 break; 94 } 95 case "AboutLoginsUpdateLogin": { 96 this.#aboutLoginsUpdateLogin(event.detail); 97 break; 98 } 99 } 100 } 101 102 #aboutLoginsInit() { 103 this.sendAsyncMessage("AboutLogins:Subscribe"); 104 105 let win = this.browsingContext.window; 106 let waivedContent = Cu.waiveXrays(win); 107 let that = this; 108 let AboutLoginsUtils = { 109 doLoginsMatch(loginA, loginB) { 110 return LoginHelper.doLoginsMatch(loginA, loginB, {}); 111 }, 112 getLoginOrigin(uriString) { 113 return LoginHelper.getLoginOrigin(uriString); 114 }, 115 setFocus(element) { 116 Services.focus.setFocus(element, Services.focus.FLAG_BYKEY); 117 }, 118 /** 119 * Shows the Primary Password prompt if enabled, or the 120 * OS auth dialog otherwise. 121 * 122 * @param resolve Callback that is called with result of authentication. 123 * @param messageId The string ID that corresponds to a string stored in aboutLogins.ftl. 124 * This string will be displayed only when the OS auth dialog is used. 125 * @param reason The reason for requesting reauthentication, used for telemetry. 126 */ 127 async promptForPrimaryPassword(resolve, messageId, reason) { 128 gPrimaryPasswordPromise = { 129 resolve, 130 }; 131 132 that.sendAsyncMessage("AboutLogins:PrimaryPasswordRequest", { 133 messageId, 134 reason, 135 }); 136 137 return gPrimaryPasswordPromise; 138 }, 139 // Default to enabled just in case a search is attempted before we get a response. 140 primaryPasswordEnabled: true, 141 passwordRevealVisible: true, 142 }; 143 waivedContent.AboutLoginsUtils = Cu.cloneInto( 144 AboutLoginsUtils, 145 waivedContent, 146 { 147 cloneFunctions: true, 148 } 149 ); 150 } 151 152 #aboutLoginsImportReportInit() { 153 this.sendAsyncMessage("AboutLogins:ImportReportInit"); 154 } 155 156 #aboutLoginsCopyLoginDetail(detail) { 157 lazy.ClipboardHelper.copyString( 158 detail, 159 this.windowContext, 160 lazy.ClipboardHelper.Sensitive 161 ); 162 } 163 164 #aboutLoginsCreateLogin(login) { 165 this.sendAsyncMessage("AboutLogins:CreateLogin", { 166 login, 167 }); 168 } 169 170 #aboutLoginsDeleteLogin(login) { 171 this.sendAsyncMessage("AboutLogins:DeleteLogin", { 172 login, 173 }); 174 } 175 176 #aboutLoginsExportPasswords() { 177 this.sendAsyncMessage("AboutLogins:ExportPasswords"); 178 } 179 180 #aboutLoginsGetHelp() { 181 this.sendAsyncMessage("AboutLogins:GetHelp"); 182 } 183 184 #aboutLoginsImportFromBrowser() { 185 this.sendAsyncMessage("AboutLogins:ImportFromBrowser"); 186 recordTelemetryEvent({ 187 name: "mgmtMenuItemUsedImportFromBrowser", 188 }); 189 } 190 191 #aboutLoginsImportFromFile() { 192 this.sendAsyncMessage("AboutLogins:ImportFromFile"); 193 recordTelemetryEvent({ 194 name: "mgmtMenuItemUsedImportFromCsv", 195 }); 196 } 197 198 #aboutLoginsOpenPreferences() { 199 this.sendAsyncMessage("AboutLogins:OpenPreferences"); 200 recordTelemetryEvent({ 201 name: "mgmtMenuItemUsedPreferences", 202 }); 203 } 204 205 #aboutLoginsRecordTelemetryEvent(event) { 206 if (event.detail.name.startsWith("openManagement")) { 207 let { docShell } = this.browsingContext; 208 // Compare to the last time open_management was recorded for the same 209 // outerWindowID to not double-count them due to a redirect to remove 210 // the entryPoint query param (since replaceState isn't allowed for 211 // about:). Don't use performance.now for the tab since you can't 212 // compare that number between different tabs and this JSM is shared. 213 let now = docShell.now(); 214 if ( 215 this.browsingContext.browserId == gLastOpenManagementBrowserId && 216 now - gLastOpenManagementEventTime < 217 TELEMETRY_MIN_MS_BETWEEN_OPEN_MANAGEMENT 218 ) { 219 return; 220 } 221 gLastOpenManagementEventTime = now; 222 gLastOpenManagementBrowserId = this.browsingContext.browserId; 223 } 224 recordTelemetryEvent(event.detail); 225 } 226 227 #aboutLoginsRemoveAllLogins() { 228 this.sendAsyncMessage("AboutLogins:RemoveAllLogins"); 229 } 230 231 #aboutLoginsSortChanged(detail) { 232 this.sendAsyncMessage("AboutLogins:SortChanged", detail); 233 } 234 235 #aboutLoginsSyncEnable() { 236 this.sendAsyncMessage("AboutLogins:SyncEnable"); 237 } 238 239 #aboutLoginsUpdateLogin(login) { 240 this.sendAsyncMessage("AboutLogins:UpdateLogin", { 241 login, 242 }); 243 } 244 245 // eslint-disable-next-line consistent-return 246 receiveMessage(message) { 247 switch (message.name) { 248 case "AboutLogins:ImportReportData": 249 this.#importReportData(message.data); 250 break; 251 case "AboutLogins:PrimaryPasswordResponse": 252 this.#primaryPasswordResponse(message.data); 253 break; 254 case "AboutLogins:RemaskPassword": 255 this.#remaskPassword(message.data); 256 break; 257 case "AboutLogins:Setup": 258 this.#setup(message.data); 259 break; 260 case "AboutLogins:WaitForFocus": { 261 return new Promise(resolve => { 262 if (!this.document.hasFocus()) { 263 this.document.ownerGlobal.addEventListener( 264 "focus", 265 () => { 266 resolve(); 267 }, 268 { once: true } 269 ); 270 } else { 271 resolve(); 272 } 273 }); 274 } 275 default: 276 this.#passMessageDataToContent(message); 277 } 278 } 279 280 #importReportData(data) { 281 this.sendToContent("ImportReportData", data); 282 } 283 284 #primaryPasswordResponse(data) { 285 if (gPrimaryPasswordPromise) { 286 gPrimaryPasswordPromise.resolve(data.result); 287 recordTelemetryEvent(data.telemetryEvent); 288 } 289 } 290 291 #remaskPassword(data) { 292 this.sendToContent("RemaskPassword", data); 293 } 294 295 #setup(data) { 296 let utils = Cu.waiveXrays(this.browsingContext.window).AboutLoginsUtils; 297 utils.primaryPasswordEnabled = data.primaryPasswordEnabled; 298 utils.passwordRevealVisible = data.passwordRevealVisible; 299 utils.importVisible = data.importVisible; 300 utils.supportBaseURL = Services.urlFormatter.formatURLPref( 301 "app.support.baseURL" 302 ); 303 this.sendToContent("Setup", data); 304 } 305 306 #passMessageDataToContent(message) { 307 this.sendToContent(message.name.replace("AboutLogins:", ""), message.data); 308 } 309 310 sendToContent(messageType, detail) { 311 let win = this.document.defaultView; 312 let message = Object.assign({ messageType }, { value: detail }); 313 let event = new win.CustomEvent("AboutLoginsChromeToContent", { 314 detail: Cu.cloneInto(message, win), 315 }); 316 win.dispatchEvent(event); 317 } 318 }