Bug 1944416: Restore individual tabs from closed groups in closed windows r=dao,sessi...
[gecko.git] / browser / components / aboutlogins / AboutLoginsChild.sys.mjs
blobac8b5ea1d7db6631417dad99ccc8578c7a9eec7e
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";
9 const lazy = {};
11 XPCOMUtils.defineLazyServiceGetter(
12   lazy,
13   "ClipboardHelper",
14   "@mozilla.org/widget/clipboardhelper;1",
15   "nsIClipboardHelper"
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) {
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   }
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   }
102   #aboutLoginsInit() {
103     this.sendAsyncMessage("AboutLogins:Subscribe");
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        * @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.
125        */
126       async promptForPrimaryPassword(resolve, messageId, reason) {
127         gPrimaryPasswordPromise = {
128           resolve,
129         };
131         that.sendAsyncMessage("AboutLogins:PrimaryPasswordRequest", {
132           messageId,
133           reason,
134         });
136         return gPrimaryPasswordPromise;
137       },
138       // Default to enabled just in case a search is attempted before we get a response.
139       primaryPasswordEnabled: true,
140       passwordRevealVisible: true,
141     };
142     waivedContent.AboutLoginsUtils = Cu.cloneInto(
143       AboutLoginsUtils,
144       waivedContent,
145       {
146         cloneFunctions: true,
147       }
148     );
149   }
151   #aboutLoginsImportReportInit() {
152     this.sendAsyncMessage("AboutLogins:ImportReportInit");
153   }
155   #aboutLoginsCopyLoginDetail(detail) {
156     lazy.ClipboardHelper.copyString(
157       detail,
158       this.windowContext,
159       lazy.ClipboardHelper.Sensitive
160     );
161   }
163   #aboutLoginsCreateLogin(login) {
164     this.sendAsyncMessage("AboutLogins:CreateLogin", {
165       login,
166     });
167   }
169   #aboutLoginsDeleteLogin(login) {
170     this.sendAsyncMessage("AboutLogins:DeleteLogin", {
171       login,
172     });
173   }
175   #aboutLoginsExportPasswords() {
176     this.sendAsyncMessage("AboutLogins:ExportPasswords");
177   }
179   #aboutLoginsGetHelp() {
180     this.sendAsyncMessage("AboutLogins:GetHelp");
181   }
183   #aboutLoginsImportFromBrowser() {
184     this.sendAsyncMessage("AboutLogins:ImportFromBrowser");
185     recordTelemetryEvent({
186       name: "mgmtMenuItemUsedImportFromBrowser",
187     });
188   }
190   #aboutLoginsImportFromFile() {
191     this.sendAsyncMessage("AboutLogins:ImportFromFile");
192     recordTelemetryEvent({
193       name: "mgmtMenuItemUsedImportFromCsv",
194     });
195   }
197   #aboutLoginsOpenPreferences() {
198     this.sendAsyncMessage("AboutLogins:OpenPreferences");
199     recordTelemetryEvent({
200       name: "mgmtMenuItemUsedPreferences",
201     });
202   }
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();
213       if (
214         this.browsingContext.browserId == gLastOpenManagementBrowserId &&
215         now - gLastOpenManagementEventTime <
216           TELEMETRY_MIN_MS_BETWEEN_OPEN_MANAGEMENT
217       ) {
218         return;
219       }
220       gLastOpenManagementEventTime = now;
221       gLastOpenManagementBrowserId = this.browsingContext.browserId;
222     }
223     recordTelemetryEvent(event.detail);
224   }
226   #aboutLoginsRemoveAllLogins() {
227     this.sendAsyncMessage("AboutLogins:RemoveAllLogins");
228   }
230   #aboutLoginsSortChanged(detail) {
231     this.sendAsyncMessage("AboutLogins:SortChanged", detail);
232   }
234   #aboutLoginsSyncEnable() {
235     this.sendAsyncMessage("AboutLogins:SyncEnable");
236   }
238   #aboutLoginsUpdateLogin(login) {
239     this.sendAsyncMessage("AboutLogins:UpdateLogin", {
240       login,
241     });
242   }
244   receiveMessage(message) {
245     switch (message.name) {
246       case "AboutLogins:ImportReportData":
247         this.#importReportData(message.data);
248         break;
249       case "AboutLogins:PrimaryPasswordResponse":
250         this.#primaryPasswordResponse(message.data);
251         break;
252       case "AboutLogins:RemaskPassword":
253         this.#remaskPassword(message.data);
254         break;
255       case "AboutLogins:Setup":
256         this.#setup(message.data);
257         break;
258       default:
259         this.#passMessageDataToContent(message);
260     }
261   }
263   #importReportData(data) {
264     this.sendToContent("ImportReportData", data);
265   }
267   #primaryPasswordResponse(data) {
268     if (gPrimaryPasswordPromise) {
269       gPrimaryPasswordPromise.resolve(data.result);
270       recordTelemetryEvent(data.telemetryEvent);
271     }
272   }
274   #remaskPassword(data) {
275     this.sendToContent("RemaskPassword", data);
276   }
278   #setup(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"
285     );
286     this.sendToContent("Setup", data);
287   }
289   #passMessageDataToContent(message) {
290     this.sendToContent(message.name.replace("AboutLogins:", ""), message.data);
291   }
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),
298     });
299     win.dispatchEvent(event);
300   }