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/. */
5 import { LoginHelper } from "resource://gre/modules/LoginHelper.sys.mjs";
7 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
11 XPCOMUtils.defineLazyServiceGetter(
14 "@mozilla.org/widget/clipboardhelper;1",
18 const TELEMETRY_MIN_MS_BETWEEN_OPEN_MANAGEMENT = 5000;
20 let gLastOpenManagementBrowserId = null;
21 let gLastOpenManagementEventTime = Number.NEGATIVE_INFINITY;
22 let gPrimaryPasswordPromise;
24 function recordTelemetryEvent(event) {
26 let { name, extra = {}, value = null } = event;
30 Glean.pwmgr[name].record(extra);
32 console.error("AboutLoginsChild: error recording telemetry event:", ex);
36 export class AboutLoginsChild extends JSWindowActorChild {
39 case "AboutLoginsInit": {
40 this.#aboutLoginsInit();
43 case "AboutLoginsImportReportInit": {
44 this.#aboutLoginsImportReportInit();
47 case "AboutLoginsCopyLoginDetail": {
48 this.#aboutLoginsCopyLoginDetail(event.detail);
51 case "AboutLoginsCreateLogin": {
52 this.#aboutLoginsCreateLogin(event.detail);
55 case "AboutLoginsDeleteLogin": {
56 this.#aboutLoginsDeleteLogin(event.detail);
59 case "AboutLoginsExportPasswords": {
60 this.#aboutLoginsExportPasswords();
63 case "AboutLoginsGetHelp": {
64 this.#aboutLoginsGetHelp();
67 case "AboutLoginsImportFromBrowser": {
68 this.#aboutLoginsImportFromBrowser();
71 case "AboutLoginsImportFromFile": {
72 this.#aboutLoginsImportFromFile();
75 case "AboutLoginsOpenPreferences": {
76 this.#aboutLoginsOpenPreferences();
79 case "AboutLoginsRecordTelemetryEvent": {
80 this.#aboutLoginsRecordTelemetryEvent(event);
83 case "AboutLoginsRemoveAllLogins": {
84 this.#aboutLoginsRemoveAllLogins();
87 case "AboutLoginsSortChanged": {
88 this.#aboutLoginsSortChanged(event.detail);
91 case "AboutLoginsSyncEnable": {
92 this.#aboutLoginsSyncEnable();
95 case "AboutLoginsUpdateLogin": {
96 this.#aboutLoginsUpdateLogin(event.detail);
103 this.sendAsyncMessage("AboutLogins:Subscribe");
105 let win = this.browsingContext.window;
106 let waivedContent = Cu.waiveXrays(win);
108 let AboutLoginsUtils = {
109 doLoginsMatch(loginA, loginB) {
110 return LoginHelper.doLoginsMatch(loginA, loginB, {});
112 getLoginOrigin(uriString) {
113 return LoginHelper.getLoginOrigin(uriString);
116 Services.focus.setFocus(element, Services.focus.FLAG_BYKEY);
119 * Shows the Primary Password prompt if enabled, or the
120 * OS auth dialog otherwise.
121 * @param resolve Callback that is called with result of authentication.
122 * @param messageId The string ID that corresponds to a string stored in aboutLogins.ftl.
123 * This string will be displayed only when the OS auth dialog is used.
124 * @param reason The reason for requesting reauthentication, used for telemetry.
126 async promptForPrimaryPassword(resolve, messageId, reason) {
127 gPrimaryPasswordPromise = {
131 that.sendAsyncMessage("AboutLogins:PrimaryPasswordRequest", {
136 return gPrimaryPasswordPromise;
138 // Default to enabled just in case a search is attempted before we get a response.
139 primaryPasswordEnabled: true,
140 passwordRevealVisible: true,
142 waivedContent.AboutLoginsUtils = Cu.cloneInto(
146 cloneFunctions: true,
151 #aboutLoginsImportReportInit() {
152 this.sendAsyncMessage("AboutLogins:ImportReportInit");
155 #aboutLoginsCopyLoginDetail(detail) {
156 lazy.ClipboardHelper.copyString(
159 lazy.ClipboardHelper.Sensitive
163 #aboutLoginsCreateLogin(login) {
164 this.sendAsyncMessage("AboutLogins:CreateLogin", {
169 #aboutLoginsDeleteLogin(login) {
170 this.sendAsyncMessage("AboutLogins:DeleteLogin", {
175 #aboutLoginsExportPasswords() {
176 this.sendAsyncMessage("AboutLogins:ExportPasswords");
179 #aboutLoginsGetHelp() {
180 this.sendAsyncMessage("AboutLogins:GetHelp");
183 #aboutLoginsImportFromBrowser() {
184 this.sendAsyncMessage("AboutLogins:ImportFromBrowser");
185 recordTelemetryEvent({
186 name: "mgmtMenuItemUsedImportFromBrowser",
190 #aboutLoginsImportFromFile() {
191 this.sendAsyncMessage("AboutLogins:ImportFromFile");
192 recordTelemetryEvent({
193 name: "mgmtMenuItemUsedImportFromCsv",
197 #aboutLoginsOpenPreferences() {
198 this.sendAsyncMessage("AboutLogins:OpenPreferences");
199 recordTelemetryEvent({
200 name: "mgmtMenuItemUsedPreferences",
204 #aboutLoginsRecordTelemetryEvent(event) {
205 if (event.detail.name.startsWith("openManagement")) {
206 let { docShell } = this.browsingContext;
207 // Compare to the last time open_management was recorded for the same
208 // outerWindowID to not double-count them due to a redirect to remove
209 // the entryPoint query param (since replaceState isn't allowed for
210 // about:). Don't use performance.now for the tab since you can't
211 // compare that number between different tabs and this JSM is shared.
212 let now = docShell.now();
214 this.browsingContext.browserId == gLastOpenManagementBrowserId &&
215 now - gLastOpenManagementEventTime <
216 TELEMETRY_MIN_MS_BETWEEN_OPEN_MANAGEMENT
220 gLastOpenManagementEventTime = now;
221 gLastOpenManagementBrowserId = this.browsingContext.browserId;
223 recordTelemetryEvent(event.detail);
226 #aboutLoginsRemoveAllLogins() {
227 this.sendAsyncMessage("AboutLogins:RemoveAllLogins");
230 #aboutLoginsSortChanged(detail) {
231 this.sendAsyncMessage("AboutLogins:SortChanged", detail);
234 #aboutLoginsSyncEnable() {
235 this.sendAsyncMessage("AboutLogins:SyncEnable");
238 #aboutLoginsUpdateLogin(login) {
239 this.sendAsyncMessage("AboutLogins:UpdateLogin", {
244 receiveMessage(message) {
245 switch (message.name) {
246 case "AboutLogins:ImportReportData":
247 this.#importReportData(message.data);
249 case "AboutLogins:PrimaryPasswordResponse":
250 this.#primaryPasswordResponse(message.data);
252 case "AboutLogins:RemaskPassword":
253 this.#remaskPassword(message.data);
255 case "AboutLogins:Setup":
256 this.#setup(message.data);
259 this.#passMessageDataToContent(message);
263 #importReportData(data) {
264 this.sendToContent("ImportReportData", data);
267 #primaryPasswordResponse(data) {
268 if (gPrimaryPasswordPromise) {
269 gPrimaryPasswordPromise.resolve(data.result);
270 recordTelemetryEvent(data.telemetryEvent);
274 #remaskPassword(data) {
275 this.sendToContent("RemaskPassword", data);
279 let utils = Cu.waiveXrays(this.browsingContext.window).AboutLoginsUtils;
280 utils.primaryPasswordEnabled = data.primaryPasswordEnabled;
281 utils.passwordRevealVisible = data.passwordRevealVisible;
282 utils.importVisible = data.importVisible;
283 utils.supportBaseURL = Services.urlFormatter.formatURLPref(
284 "app.support.baseURL"
286 this.sendToContent("Setup", data);
289 #passMessageDataToContent(message) {
290 this.sendToContent(message.name.replace("AboutLogins:", ""), message.data);
293 sendToContent(messageType, detail) {
294 let win = this.document.defaultView;
295 let message = Object.assign({ messageType }, { value: detail });
296 let event = new win.CustomEvent("AboutLoginsChromeToContent", {
297 detail: Cu.cloneInto(message, win),
299 win.dispatchEvent(event);