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 5var EXPORTED_SYMBOLS = ["RecentlyClosedTabsAndWindowsMenuUtils"]; 6 7const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); 8 9ChromeUtils.defineModuleGetter( 10 this, 11 "PluralForm", 12 "resource://gre/modules/PluralForm.jsm" 13); 14ChromeUtils.defineModuleGetter( 15 this, 16 "SessionStore", 17 "resource:///modules/sessionstore/SessionStore.jsm" 18); 19 20var navigatorBundle = Services.strings.createBundle( 21 "chrome://browser/locale/browser.properties" 22); 23 24var RecentlyClosedTabsAndWindowsMenuUtils = { 25 /** 26 * Builds up a document fragment of UI items for the recently closed tabs. 27 * @param aWindow 28 * The window that the tabs were closed in. 29 * @param aTagName 30 * The tag name that will be used when creating the UI items. 31 * @param aPrefixRestoreAll (defaults to false) 32 * Whether the 'restore all tabs' item is suffixed or prefixed to the list. 33 * If suffixed (the default) a separator will be inserted before it. 34 * @param aRestoreAllLabel (defaults to "appmenu-reopen-all-tabs") 35 * Which localizable string to use for the 'restore all tabs' item. 36 * @returns A document fragment with UI items for each recently closed tab. 37 */ 38 getTabsFragment( 39 aWindow, 40 aTagName, 41 aPrefixRestoreAll = false, 42 aRestoreAllLabel = "appmenu-reopen-all-tabs" 43 ) { 44 let doc = aWindow.document; 45 let fragment = doc.createDocumentFragment(); 46 if (SessionStore.getClosedTabCount(aWindow) != 0) { 47 let closedTabs = SessionStore.getClosedTabData(aWindow, false); 48 for (let i = 0; i < closedTabs.length; i++) { 49 createEntry( 50 aTagName, 51 false, 52 i, 53 closedTabs[i], 54 doc, 55 closedTabs[i].title, 56 fragment 57 ); 58 } 59 60 createRestoreAllEntry( 61 doc, 62 fragment, 63 aPrefixRestoreAll, 64 false, 65 aRestoreAllLabel, 66 closedTabs.length, 67 aTagName 68 ); 69 } 70 return fragment; 71 }, 72 73 /** 74 * Builds up a document fragment of UI items for the recently closed windows. 75 * @param aWindow 76 * A window that can be used to create the elements and document fragment. 77 * @param aTagName 78 * The tag name that will be used when creating the UI items. 79 * @param aPrefixRestoreAll (defaults to false) 80 * Whether the 'restore all windows' item is suffixed or prefixed to the list. 81 * If suffixed (the default) a separator will be inserted before it. 82 * @param aRestoreAllLabel (defaults to "appmenu-reopen-all-windows") 83 * Which localizable string to use for the 'restore all windows' item. 84 * @returns A document fragment with UI items for each recently closed window. 85 */ 86 getWindowsFragment( 87 aWindow, 88 aTagName, 89 aPrefixRestoreAll = false, 90 aRestoreAllLabel = "appmenu-reopen-all-windows" 91 ) { 92 let closedWindowData = SessionStore.getClosedWindowData(false); 93 let doc = aWindow.document; 94 let fragment = doc.createDocumentFragment(); 95 if (closedWindowData.length) { 96 let menuLabelString = navigatorBundle.GetStringFromName( 97 "menuUndoCloseWindowLabel" 98 ); 99 let menuLabelStringSingleTab = navigatorBundle.GetStringFromName( 100 "menuUndoCloseWindowSingleTabLabel" 101 ); 102 103 for (let i = 0; i < closedWindowData.length; i++) { 104 let undoItem = closedWindowData[i]; 105 let otherTabsCount = undoItem.tabs.length - 1; 106 let label = 107 otherTabsCount == 0 108 ? menuLabelStringSingleTab 109 : PluralForm.get(otherTabsCount, menuLabelString); 110 let menuLabel = label 111 .replace("#1", undoItem.title) 112 .replace("#2", otherTabsCount); 113 let selectedTab = undoItem.tabs[undoItem.selected - 1]; 114 115 createEntry(aTagName, true, i, selectedTab, doc, menuLabel, fragment); 116 } 117 118 createRestoreAllEntry( 119 doc, 120 fragment, 121 aPrefixRestoreAll, 122 true, 123 aRestoreAllLabel, 124 closedWindowData.length, 125 aTagName 126 ); 127 } 128 return fragment; 129 }, 130 131 /** 132 * Re-open a closed tab and put it to the end of the tab strip. 133 * Used for a middle click. 134 * @param aEvent 135 * The event when the user clicks the menu item 136 */ 137 _undoCloseMiddleClick(aEvent) { 138 if (aEvent.button != 1) { 139 return; 140 } 141 142 aEvent.view.undoCloseTab(aEvent.originalTarget.getAttribute("value")); 143 aEvent.view.gBrowser.moveTabToEnd(); 144 let ancestorPanel = aEvent.target.closest("panel"); 145 if (ancestorPanel) { 146 ancestorPanel.hidePopup(); 147 } 148 }, 149 150 get strings() { 151 delete this.strings; 152 return (this.strings = new Localization( 153 ["branding/brand.ftl", "browser/menubar.ftl", "browser/appmenu.ftl"], 154 true 155 )); 156 }, 157}; 158 159function setImage(aItem, aElement) { 160 let iconURL = aItem.image; 161 // don't initiate a connection just to fetch a favicon (see bug 467828) 162 if (/^https?:/.test(iconURL)) { 163 iconURL = "moz-anno:favicon:" + iconURL; 164 } 165 166 aElement.setAttribute("image", iconURL); 167} 168 169/** 170 * Create a UI entry for a recently closed tab or window. 171 * @param aTagName 172 * the tag name that will be used when creating the UI entry 173 * @param aIsWindowsFragment 174 * whether or not this entry will represent a closed window 175 * @param aIndex 176 * the index of the closed tab 177 * @param aClosedTab 178 * the closed tab 179 * @param aDocument 180 * a document that can be used to create the entry 181 * @param aMenuLabel 182 * the label the created entry will have 183 * @param aFragment 184 * the fragment the created entry will be in 185 */ 186function createEntry( 187 aTagName, 188 aIsWindowsFragment, 189 aIndex, 190 aClosedTab, 191 aDocument, 192 aMenuLabel, 193 aFragment 194) { 195 let element = aDocument.createXULElement(aTagName); 196 197 element.setAttribute("label", aMenuLabel); 198 if (aClosedTab.image) { 199 setImage(aClosedTab, element); 200 } 201 if (!aIsWindowsFragment) { 202 element.setAttribute("value", aIndex); 203 } 204 205 if (aTagName == "menuitem") { 206 element.setAttribute( 207 "class", 208 "menuitem-iconic bookmark-item menuitem-with-favicon" 209 ); 210 } 211 212 element.setAttribute( 213 "oncommand", 214 "undoClose" + (aIsWindowsFragment ? "Window" : "Tab") + "(" + aIndex + ");" 215 ); 216 217 // Set the targetURI attribute so it will be shown in tooltip. 218 // SessionStore uses one-based indexes, so we need to normalize them. 219 let tabData; 220 tabData = aIsWindowsFragment ? aClosedTab : aClosedTab.state; 221 let activeIndex = (tabData.index || tabData.entries.length) - 1; 222 if (activeIndex >= 0 && tabData.entries[activeIndex]) { 223 element.setAttribute("targetURI", tabData.entries[activeIndex].url); 224 } 225 226 // Windows don't open in new tabs and menuitems dispatch command events on 227 // middle click, so we only need to manually handle middle clicks for 228 // toolbarbuttons. 229 if (!aIsWindowsFragment && aTagName != "menuitem") { 230 element.addEventListener( 231 "click", 232 RecentlyClosedTabsAndWindowsMenuUtils._undoCloseMiddleClick 233 ); 234 } 235 236 if (aIndex == 0) { 237 element.setAttribute( 238 "key", 239 "key_undoClose" + (aIsWindowsFragment ? "Window" : "Tab") 240 ); 241 } 242 243 aFragment.appendChild(element); 244} 245 246/** 247 * Create an entry to restore all closed windows or tabs. 248 * @param aDocument 249 * a document that can be used to create the entry 250 * @param aFragment 251 * the fragment the created entry will be in 252 * @param aPrefixRestoreAll 253 * whether the 'restore all windows' item is suffixed or prefixed to the list 254 * If suffixed a separator will be inserted before it. 255 * @param aIsWindowsFragment 256 * whether or not this entry will represent a closed window 257 * @param aRestoreAllLabel 258 * which localizable string to use for the entry 259 * @param aEntryCount 260 * the number of elements to be restored by this entry 261 * @param aTagName 262 * the tag name that will be used when creating the UI entry 263 */ 264function createRestoreAllEntry( 265 aDocument, 266 aFragment, 267 aPrefixRestoreAll, 268 aIsWindowsFragment, 269 aRestoreAllLabel, 270 aEntryCount, 271 aTagName 272) { 273 let restoreAllElements = aDocument.createXULElement(aTagName); 274 restoreAllElements.classList.add("restoreallitem"); 275 276 // We cannot use aDocument.l10n.setAttributes because the menubar label is not 277 // updated in time and displays a blank string (see Bug 1691553). 278 restoreAllElements.setAttribute( 279 "label", 280 RecentlyClosedTabsAndWindowsMenuUtils.strings.formatValueSync( 281 aRestoreAllLabel 282 ) 283 ); 284 285 restoreAllElements.setAttribute( 286 "oncommand", 287 "for (var i = 0; i < " + 288 aEntryCount + 289 "; i++) undoClose" + 290 (aIsWindowsFragment ? "Window" : "Tab") + 291 "();" 292 ); 293 if (aPrefixRestoreAll) { 294 aFragment.insertBefore(restoreAllElements, aFragment.firstChild); 295 } else { 296 aFragment.appendChild(aDocument.createXULElement("menuseparator")); 297 aFragment.appendChild(restoreAllElements); 298 } 299} 300