Backed out changeset 496886cb30a5 (bug 1867152) for bc failures on browser_user_input...
[gecko.git] / browser / components / sessionstore / RecentlyClosedTabsAndWindowsMenuUtils.sys.mjs
blob4d53b166c0d628c43d3d25cf9af8d7684b9345ec
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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
7 const lazy = {};
9 ChromeUtils.defineESModuleGetters(lazy, {
10   PlacesUIUtils: "resource:///modules/PlacesUIUtils.sys.mjs",
11   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
12   SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
13 });
15 ChromeUtils.defineLazyGetter(lazy, "l10n", () => {
16   return new Localization(["browser/recentlyClosed.ftl"], true);
17 });
19 XPCOMUtils.defineLazyPreferenceGetter(
20   lazy,
21   "closedTabsFromAllWindowsEnabled",
22   "browser.sessionstore.closedTabsFromAllWindows"
25 XPCOMUtils.defineLazyPreferenceGetter(
26   lazy,
27   "closedTabsFromClosedWindowsEnabled",
28   "browser.sessionstore.closedTabsFromClosedWindows"
31 export var RecentlyClosedTabsAndWindowsMenuUtils = {
32   /**
33    * Builds up a document fragment of UI items for the recently closed tabs.
34    * @param   aWindow
35    *          The window that the tabs were closed in.
36    * @param   aTagName
37    *          The tag name that will be used when creating the UI items.
38    * @param   aPrefixRestoreAll (defaults to false)
39    *          Whether the 'restore all tabs' item is suffixed or prefixed to the list.
40    *          If suffixed (the default) a separator will be inserted before it.
41    * @returns A document fragment with UI items for each recently closed tab.
42    */
43   getTabsFragment(aWindow, aTagName, aPrefixRestoreAll = false) {
44     let doc = aWindow.document;
45     const isPrivate = lazy.PrivateBrowsingUtils.isWindowPrivate(aWindow);
46     const fragment = doc.createDocumentFragment();
47     let isEmpty = true;
49     if (
50       lazy.SessionStore.getClosedTabCount({
51         sourceWindow: aWindow,
52         closedTabsFromClosedWindows: false,
53       })
54     ) {
55       isEmpty = false;
56       const browserWindows = lazy.closedTabsFromAllWindowsEnabled
57         ? lazy.SessionStore.getWindows(aWindow)
58         : [aWindow];
60       for (const win of browserWindows) {
61         const closedTabs = lazy.SessionStore.getClosedTabDataForWindow(win);
62         for (let i = 0; i < closedTabs.length; i++) {
63           createEntry(
64             aTagName,
65             false,
66             i,
67             closedTabs[i],
68             doc,
69             closedTabs[i].title,
70             fragment
71           );
72         }
73       }
74     }
76     if (
77       !isPrivate &&
78       lazy.closedTabsFromClosedWindowsEnabled &&
79       lazy.SessionStore.getClosedTabCountFromClosedWindows()
80     ) {
81       isEmpty = false;
82       const closedTabs = lazy.SessionStore.getClosedTabDataFromClosedWindows();
83       for (let i = 0; i < closedTabs.length; i++) {
84         createEntry(
85           aTagName,
86           false,
87           i,
88           closedTabs[i],
89           doc,
90           closedTabs[i].title,
91           fragment
92         );
93       }
94     }
96     if (!isEmpty) {
97       createRestoreAllEntry(
98         doc,
99         fragment,
100         aPrefixRestoreAll,
101         false,
102         aTagName == "menuitem"
103           ? "recently-closed-menu-reopen-all-tabs"
104           : "recently-closed-panel-reopen-all-tabs",
105         aTagName
106       );
107     }
108     return fragment;
109   },
111   /**
112    * Builds up a document fragment of UI items for the recently closed windows.
113    * @param   aWindow
114    *          A window that can be used to create the elements and document fragment.
115    * @param   aTagName
116    *          The tag name that will be used when creating the UI items.
117    * @param   aPrefixRestoreAll (defaults to false)
118    *          Whether the 'restore all windows' item is suffixed or prefixed to the list.
119    *          If suffixed (the default) a separator will be inserted before it.
120    * @returns A document fragment with UI items for each recently closed window.
121    */
122   getWindowsFragment(aWindow, aTagName, aPrefixRestoreAll = false) {
123     let closedWindowData = lazy.SessionStore.getClosedWindowData();
124     let doc = aWindow.document;
125     let fragment = doc.createDocumentFragment();
126     if (closedWindowData.length) {
127       for (let i = 0; i < closedWindowData.length; i++) {
128         const { selected, tabs, title } = closedWindowData[i];
129         const selectedTab = tabs[selected - 1];
130         if (selectedTab) {
131           const menuLabel = lazy.l10n.formatValueSync(
132             "recently-closed-undo-close-window-label",
133             { tabCount: tabs.length - 1, winTitle: title }
134           );
135           createEntry(aTagName, true, i, selectedTab, doc, menuLabel, fragment);
136         }
137       }
139       createRestoreAllEntry(
140         doc,
141         fragment,
142         aPrefixRestoreAll,
143         true,
144         aTagName == "menuitem"
145           ? "recently-closed-menu-reopen-all-windows"
146           : "recently-closed-panel-reopen-all-windows",
147         aTagName
148       );
149     }
150     return fragment;
151   },
153   /**
154    * Handle a command event to re-open all closed tabs
155    * @param aEvent
156    *        The command event when the user clicks the restore all menu item
157    */
158   onRestoreAllTabsCommand(aEvent) {
159     const currentWindow = aEvent.target.ownerGlobal;
160     const browserWindows = lazy.closedTabsFromAllWindowsEnabled
161       ? lazy.SessionStore.getWindows(currentWindow)
162       : [currentWindow];
163     for (const sourceWindow of browserWindows) {
164       let count = lazy.SessionStore.getClosedTabCountForWindow(sourceWindow);
165       while (--count >= 0) {
166         lazy.SessionStore.undoCloseTab(sourceWindow, 0, currentWindow);
167       }
168     }
169     if (lazy.closedTabsFromClosedWindowsEnabled) {
170       for (let tabData of lazy.SessionStore.getClosedTabDataFromClosedWindows()) {
171         lazy.SessionStore.undoClosedTabFromClosedWindow(
172           { sourceClosedId: tabData.sourceClosedId },
173           tabData.closedId,
174           currentWindow
175         );
176       }
177     }
178   },
180   /**
181    * Handle a command event to re-open all closed windows
182    * @param aEvent
183    *        The command event when the user clicks the restore all menu item
184    */
185   onRestoreAllWindowsCommand(aEvent) {
186     const count = lazy.SessionStore.getClosedWindowCount();
187     for (let index = 0; index < count; index++) {
188       lazy.SessionStore.undoCloseWindow(index);
189     }
190   },
192   /**
193    * Re-open a closed tab and put it to the end of the tab strip.
194    * Used for a middle click.
195    * @param aEvent
196    *        The event when the user clicks the menu item
197    */
198   _undoCloseMiddleClick(aEvent) {
199     if (aEvent.button != 1) {
200       return;
201     }
202     if (aEvent.originalTarget.hasAttribute("source-closed-id")) {
203       lazy.SessionStore.undoClosedTabFromClosedWindow(
204         {
205           sourceClosedId:
206             aEvent.originalTarget.getAttribute("source-closed-id"),
207         },
208         aEvent.originalTarget.getAttribute("value")
209       );
210     } else {
211       aEvent.view.undoCloseTab(
212         aEvent.originalTarget.getAttribute("value"),
213         aEvent.originalTarget.getAttribute("source-window-id")
214       );
215     }
216     aEvent.view.gBrowser.moveTabToEnd();
217     let ancestorPanel = aEvent.target.closest("panel");
218     if (ancestorPanel) {
219       ancestorPanel.hidePopup();
220     }
221   },
225  * Create a UI entry for a recently closed tab or window.
226  * @param aTagName
227  *        the tag name that will be used when creating the UI entry
228  * @param aIsWindowsFragment
229  *        whether or not this entry will represent a closed window
230  * @param aIndex
231  *        the index of the closed tab
232  * @param aClosedTab
233  *        the closed tab
234  * @param aDocument
235  *        a document that can be used to create the entry
236  * @param aMenuLabel
237  *        the label the created entry will have
238  * @param aFragment
239  *        the fragment the created entry will be in
240  */
241 function createEntry(
242   aTagName,
243   aIsWindowsFragment,
244   aIndex,
245   aClosedTab,
246   aDocument,
247   aMenuLabel,
248   aFragment
249 ) {
250   let element = aDocument.createXULElement(aTagName);
252   element.setAttribute("label", aMenuLabel);
253   if (aClosedTab.image) {
254     const iconURL = lazy.PlacesUIUtils.getImageURL(aClosedTab.image);
255     element.setAttribute("image", iconURL);
256   }
258   if (aIsWindowsFragment) {
259     element.setAttribute("oncommand", `undoCloseWindow("${aIndex}");`);
260   } else if (typeof aClosedTab.sourceClosedId == "number") {
261     // sourceClosedId is used to look up the closed window to remove it when the tab is restored
262     let sourceClosedId = aClosedTab.sourceClosedId;
263     element.setAttribute("source-closed-id", sourceClosedId);
264     element.setAttribute("value", aClosedTab.closedId);
265     element.removeAttribute("oncommand");
266     element.addEventListener(
267       "command",
268       event => {
269         lazy.SessionStore.undoClosedTabFromClosedWindow(
270           { sourceClosedId },
271           aClosedTab.closedId
272         );
273       },
274       { once: true }
275     );
276   } else {
277     // sourceWindowId is used to look up the closed tab entry to remove it when it is restored
278     let sourceWindowId = aClosedTab.sourceWindowId;
279     element.setAttribute("value", aIndex);
280     element.setAttribute("source-window-id", sourceWindowId);
281     element.setAttribute(
282       "oncommand",
283       `undoCloseTab(${aIndex}, "${sourceWindowId}");`
284     );
285   }
287   if (aTagName == "menuitem") {
288     element.setAttribute(
289       "class",
290       "menuitem-iconic bookmark-item menuitem-with-favicon"
291     );
292   }
294   // Set the targetURI attribute so it will be shown in tooltip.
295   // SessionStore uses one-based indexes, so we need to normalize them.
296   let tabData;
297   tabData = aIsWindowsFragment ? aClosedTab : aClosedTab.state;
298   let activeIndex = (tabData.index || tabData.entries.length) - 1;
299   if (activeIndex >= 0 && tabData.entries[activeIndex]) {
300     element.setAttribute("targetURI", tabData.entries[activeIndex].url);
301   }
303   // Windows don't open in new tabs and menuitems dispatch command events on
304   // middle click, so we only need to manually handle middle clicks for
305   // toolbarbuttons.
306   if (!aIsWindowsFragment && aTagName != "menuitem") {
307     element.addEventListener(
308       "click",
309       RecentlyClosedTabsAndWindowsMenuUtils._undoCloseMiddleClick
310     );
311   }
313   if (aIndex == 0) {
314     element.setAttribute(
315       "key",
316       "key_undoClose" + (aIsWindowsFragment ? "Window" : "Tab")
317     );
318   }
320   aFragment.appendChild(element);
324  * Create an entry to restore all closed windows or tabs.
325  * @param aDocument
326  *        a document that can be used to create the entry
327  * @param aFragment
328  *        the fragment the created entry will be in
329  * @param aPrefixRestoreAll
330  *        whether the 'restore all windows' item is suffixed or prefixed to the list
331  *        If suffixed a separator will be inserted before it.
332  * @param aIsWindowsFragment
333  *        whether or not this entry will represent a closed window
334  * @param aRestoreAllLabel
335  *        which localizable string to use for the entry
336  * @param aEntryCount
337  *        the number of elements to be restored by this entry
338  * @param aTagName
339  *        the tag name that will be used when creating the UI entry
340  */
341 function createRestoreAllEntry(
342   aDocument,
343   aFragment,
344   aPrefixRestoreAll,
345   aIsWindowsFragment,
346   aRestoreAllLabel,
347   aTagName
348 ) {
349   let restoreAllElements = aDocument.createXULElement(aTagName);
350   restoreAllElements.classList.add("restoreallitem");
352   // We cannot use aDocument.l10n.setAttributes because the menubar label is not
353   // updated in time and displays a blank string (see Bug 1691553).
354   restoreAllElements.setAttribute(
355     "label",
356     lazy.l10n.formatValueSync(aRestoreAllLabel)
357   );
359   restoreAllElements.addEventListener(
360     "command",
361     aIsWindowsFragment
362       ? RecentlyClosedTabsAndWindowsMenuUtils.onRestoreAllWindowsCommand
363       : RecentlyClosedTabsAndWindowsMenuUtils.onRestoreAllTabsCommand
364   );
366   if (aPrefixRestoreAll) {
367     aFragment.insertBefore(restoreAllElements, aFragment.firstChild);
368   } else {
369     aFragment.appendChild(aDocument.createXULElement("menuseparator"));
370     aFragment.appendChild(restoreAllElements);
371   }