Advertisement
Guest User

Untitled

a guest
Nov 6th, 2021
82
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 34.76 KB | None | 0 0
  1. class TabTooltipNav {
  2. static config = {
  3. // if you only want the popup to open while you have a modifier key pressed, type it here.
  4. // accepted values are "ctrl", "shift", "alt", "meta", and "accel". combinations are not accepted.
  5. // if you want a modifier key, the value must be surrounded by quotes. don't delete the comma after the value.
  6. // if you don't want a modifier key, change this to false — with no quotes. but don't delete the comma.
  7. "Modifier key": false,
  8.  
  9. // if you want the normal tooltip to show when hovering a tab without the modifier key, set this to true.
  10. // if you want no tooltip to show at all unless the modifier key is pressed, set this to false.
  11. // it will have no effect if "Modifier key" is not set to one of the valid string values listed above.
  12. "Show vanilla tooltip if modifier is not pressed": true,
  13.  
  14. // When you right click one of the back or forward buttons, it opens a little context menu
  15. // that shows up to 15 items in that tab's history. when you mouse over them, the popup's tooltip
  16. // will be temporarily updated to show that history item's title, URL, and favicon.
  17. // this is similar to how the built-in back/forward buttons' menu works, where hovering a menuitem
  18. // causes the URL to display in the status bar at the bottom of the screen.
  19. // if you don't need this behavior or find it annoying, set this pref to false.
  20. "Update tooltip when hovering in the history menu": true,
  21.  
  22. l10n: {
  23. "Go Back (Single Tab)": "Navigate tab back one page",
  24.  
  25. "Go Back (Multiselected)": "Navigate selected tabs back one page",
  26.  
  27. "Go Forward (Single Tab)": "Navigate tab forward one page",
  28.  
  29. "Go Forward (Multiselected)": "Navigate selected tabs forward one page",
  30.  
  31. "Reload (Single Tab)": "Reload tab",
  32.  
  33. "Reload (Multiselected)": "Reload selected tabs",
  34. },
  35. // firefox doesn't have localized strings for these menuitems,
  36. // since it doesn't have any user-facing features like this
  37. // where you can navigate tabs that aren't currently active/selected.
  38. // nor does it have any ability to navigate multiple tabs at once.
  39. // so you have to localize the tooltips yourself to match your browser language.
  40. };
  41. /**
  42. * create a DOM node with given parameters
  43. * @param {object} aDoc (which document to create the element in)
  44. * @param {string} tag (an HTML tag name, like "button" or "p")
  45. * @param {object} props (an object containing attribute name/value pairs, e.g. class: ".bookmark-item")
  46. * @param {boolean} isHTML (if true, create an HTML element. if omitted or false, create a XUL element. generally avoid HTML when modding the UI, most UI elements are actually XUL elements.)
  47. * @returns the created DOM node
  48. */
  49. create(aDoc, tag, props, isHTML = false) {
  50. let el = isHTML ? aDoc.createElement(tag) : aDoc.createXULElement(tag);
  51. for (let prop in props) {
  52. el.setAttribute(prop, props[prop]);
  53. }
  54. return el;
  55. }
  56. /**
  57. * set a list of attributes on a DOM node
  58. * @param {object} element (a DOM node)
  59. * @param {object} attrs (an object containing properties where the key is the attribute name and the value is the attribute value)
  60. */
  61. setAttributes(element, attrs) {
  62. for (let [name, value] of Object.entries(attrs))
  63. if (value) element.setAttribute(name, value);
  64. else element.removeAttribute(name);
  65. }
  66. // if there are multiple tabs selected and the trigger tab (the hovered one) is one of them,
  67. // return the full array of selected tabs. if the trigger tab is not one of them,
  68. // or if only one tab is selected, return an array containing just the trigger tab.
  69. get tabs() {
  70. if (!this.triggerTab) return [];
  71. if (this.triggerTab.multiselected) return gBrowser.selectedTabs;
  72. else return [this.triggerTab];
  73. }
  74. get navPopup() {
  75. return this._navPopup || (this._navPopup = document.querySelector("#tab-nav-popup"));
  76. }
  77. get tabBackForwardMenu() {
  78. return (
  79. this._tabBackForwardMenu ||
  80. (this._tabBackForwardMenu = document.querySelector("#tabBackForwardMenu"))
  81. );
  82. }
  83. get tooltipBox() {
  84. return (
  85. this._tooltipBox ||
  86. (this._tooltipBox = this.navPopup.querySelector("#tab-nav-tooltip-box"))
  87. );
  88. }
  89. get backButton() {
  90. return (
  91. this._backButton || (this._backButton = this.navPopup.querySelector("#tab-nav-back"))
  92. );
  93. }
  94. get forwardButton() {
  95. return (
  96. this._forwardButton ||
  97. (this._forwardButton = this.navPopup.querySelector("#tab-nav-forward"))
  98. );
  99. }
  100. get reloadButton() {
  101. return (
  102. this._reloadButton ||
  103. (this._reloadButton = this.navPopup.querySelector("#tab-nav-reload"))
  104. );
  105. }
  106. get favicon() {
  107. return (
  108. this._favicon ||
  109. (this._favicon = this.navPopup.querySelector("#tab-nav-tooltip-favicon"))
  110. );
  111. }
  112. set knownWidth(val) {
  113. if (val) {
  114. this.navPopup.style.setProperty("--tab-nav-known-width", val + "px");
  115. this._knownWidth = val;
  116. } else {
  117. this.navPopup.style.removeProperty("--tab-nav-known-width");
  118. this._knownWidth = 0;
  119. }
  120. }
  121. get knownWidth() {
  122. return this._knownWidth;
  123. }
  124. constructor() {
  125. this.config = TabTooltipNav.config;
  126. let l10n = this.config.l10n;
  127. XPCOMUtils.defineLazyPreferenceGetter(
  128. this,
  129. "popupDelay",
  130. "ui.tooltipDelay",
  131. 500,
  132. null,
  133. (val) => Math.max(val - 180, 0)
  134. );
  135. XPCOMUtils.defineLazyPreferenceGetter(
  136. this,
  137. "hideDelay",
  138. "userChrome.tabs.tabTooltipNavButtons.hover-out-delay",
  139. 500,
  140. null,
  141. (val) => Math.max(val - 180, 0)
  142. );
  143. this.registerSheet();
  144. this.markup = `<panel id="tab-nav-popup" class="panel-no-padding" type="arrow" role="group" noautofocus="true"
  145. aria-labelledby="tab-nav-tooltip-label" onpopupshowing="tabNavButtons.onPopupShowing(event);"
  146. onpopupshown="tabNavButtons.onPopupShown(event);" onpopuphidden="tabNavButtons.onPopupHidden(event);"
  147. onmouseleave="tabNavButtons.onMouseleave(event);" consumeoutsideclicks="never">
  148. <hbox id="tab-nav-popup-body" class="panel-subview-body">
  149. <toolbarbutton id="tab-nav-back" class="toolbarbutton-1" tooltiptext='${l10n["Go Back (Single Tab)"]}'
  150. oncommand="tabNavButtons.goBack(event)" onclick="checkForMiddleClick(this, event);" context="tabBackForwardMenu"/>
  151. <toolbarbutton id="tab-nav-forward" class="toolbarbutton-1" tooltiptext='${l10n["Go Forward (Single Tab)"]}'
  152. oncommand="tabNavButtons.goForward(event)" onclick="checkForMiddleClick(this, event);" context="tabBackForwardMenu"/>
  153. <toolbarbutton id="tab-nav-reload" class="toolbarbutton-1" tooltiptext='${l10n["Reload (Single Tab)"]}'
  154. oncommand="tabNavButtons.reloadOrDuplicate(event)" onclick="checkForMiddleClick(this, event);"/>
  155. <separator id="tab-nav-separator" orient="vertical"/>
  156. <hbox id="tab-nav-tooltip-box" align="center">
  157. <box id="tab-nav-favicon-box">
  158. <image id="tab-nav-tooltip-favicon"></image>
  159. </box>
  160. <vbox id="tab-nav-tooltip-textbox" class="places-tooltip-box" flex="1">
  161. <description id="tab-nav-tooltip-label" class="tooltip-label places-tooltip-title"/>
  162. <hbox id="tab-nav-tooltip-uri-box">
  163. <description id="tab-nav-tooltip-uri" crop="center" class="tooltip-label places-tooltip-uri uri-element"/>
  164. </hbox>
  165. </vbox>
  166. </hbox>
  167. </hbox>
  168. </panel>
  169. <menupopup id="tabBackForwardMenu" onpopupshowing="return tabNavButtons.fillHistoryMenu(event.target);"
  170. onpopuphidden="tabNavButtons.onContextHidden();" oncommand="tabNavButtons.gotoHistoryIndex(event); event.stopPropagation();"/>`;
  171. window.mainPopupSet.appendChild(MozXULElement.parseXULToFragment(this.markup));
  172. this.navPopup.removeAttribute("position");
  173. this.navPopup.removeAttribute("side");
  174. this.navPopup.removeAttribute("flip");
  175. if (
  176. this.config["Show vanilla tooltip if modifier is not pressed"] &&
  177. /ctrl|alt|shift|meta|accel/.test(this.config["Modifier key"])
  178. )
  179. document
  180. .querySelector("#tabbrowser-tab-tooltip")
  181. .addEventListener("popupshowing", this);
  182. else gBrowser.tabContainer.removeAttribute("tooltip");
  183. [
  184. "TabClose",
  185. "TabMove",
  186. "TabSelect",
  187. "TabAttrModified",
  188. "mousemove",
  189. "mouseleave",
  190. "mousedown",
  191. ].forEach((ev) => gBrowser.tabContainer.addEventListener(ev, this));
  192. gBrowser.tabContainer.arrowScrollbox.addEventListener("scroll", this);
  193. gBrowser.addTabsProgressListener(this);
  194. }
  195. handleEvent(e) {
  196. switch (e.type) {
  197. case "mousemove":
  198. this.onMousemove(e);
  199. break;
  200. case "mouseleave":
  201. this.onMouseleave();
  202. break;
  203. case "TabClose":
  204. case "TabMove":
  205. case "TabSelect":
  206. case "scroll":
  207. case "mousedown":
  208. this.interrupt();
  209. break;
  210. case "TabAttrModified":
  211. this.onTabAttrModified(e);
  212. break;
  213. case "popupshowing":
  214. this.onTooltipShowing(e);
  215. break;
  216. default:
  217. }
  218. }
  219. // when the popup initially shows, set the labels, tooltips, and button states
  220. onPopupShowing(e) {
  221. this.isOpen = true;
  222. let l10n = this.config.l10n;
  223. let { multiselected } = this.triggerTab;
  224. let tabs = this.tabs;
  225. this.updateButtonsState(tabs);
  226. this.handleTooltip(e);
  227. this.backButton.tooltipText = multiselected
  228. ? l10n["Go Back (Multiselected)"]
  229. : l10n["Go Back (Single Tab)"];
  230. this.forwardButton.tooltipText = multiselected
  231. ? l10n["Go Forward (Multiselected)"]
  232. : l10n["Go Forward (Single Tab)"];
  233. this.reloadButton.tooltipText = multiselected
  234. ? l10n["Reload (Multiselected)"]
  235. : l10n["Reload (Single Tab)"];
  236. }
  237. onPopupShown(e) {
  238. this.isOpen = true;
  239. this.captureKnownWidth();
  240. }
  241. onPopupHidden(e) {
  242. this.isOpen = false;
  243. this.knownWidth = null;
  244. }
  245. // called when the context menu is closed for whatever reason. we need to hide the whole
  246. // nav popup if the context menu closes and the mouse is now outside the valid bounds.
  247. onContextHidden(e) {
  248. this.menuOpen = false;
  249. this.onMouseleave();
  250. }
  251. // main trigger for opening the nav popup
  252. onMousemove(e) {
  253. clearTimeout(this.openTimer);
  254. clearTimeout(this.closeTimer);
  255. if (this.menuOpen) return;
  256. let tab = e.target.closest("tab");
  257. if (this.isOpen) {
  258. if (this.triggerTab === tab) return this.handleTooltip(e);
  259. else this.closePopup();
  260. }
  261. this.triggerTab = tab;
  262. if (tab) this.openTimer = setTimeout(() => this.openPopup(e), this.popupDelay);
  263. }
  264. // main trigger for closing it
  265. onMouseleave() {
  266. clearTimeout(this.openTimer);
  267. clearTimeout(this.closeTimer);
  268. if (this.menuOpen) return;
  269. if (this.navPopup.matches(":hover") || this.triggerTab?.matches(":hover")) return;
  270. this.closeTimer = setTimeout(() => this.closePopup(), this.popupDelay);
  271. }
  272. // on navigation, update back/forward buttons and update the tooltip if the navigation involved
  273. // the trigger tab or multiselected tabs (provided the trigger tab is also multiselected)
  274. onLocationChange(browser, progress) {
  275. if (!progress.isTopLevel || !this.isOpen || !this.triggerTab) return;
  276. let tab = gBrowser.getTabForBrowser(browser);
  277. let { tabs } = this;
  278. if (tabs.indexOf(tab) > -1) this.updateButtonsState(tabs);
  279. if (tab === this.triggerTab) this.handleTooltip();
  280. }
  281. // update the nav popup tooltip if attributes of the trigger tab changed.
  282. onTabAttrModified(e) {
  283. if (e.target === this.triggerTab) this.handleTooltip();
  284. }
  285. // if the native tab tooltip is about to show, either suppress it
  286. // or allow it and prevent the nav popup from opening.
  287. onTooltipShowing(e) {
  288. if (this.isOpen || this.modifierPressed(e)) {
  289. e.preventDefault();
  290. return;
  291. } else this.interrupt();
  292. }
  293. // close all popups and bail out of any scheduled popup actions.
  294. interrupt() {
  295. clearTimeout(this.openTimer);
  296. clearTimeout(this.closeTimer);
  297. this.closePopup();
  298. this.tabBackForwardMenu.hidePopup();
  299. }
  300. openPopup(e) {
  301. if (this.isOpen || !this.modifierPressed(e)) return;
  302. if (gBrowser.tabContainer.hasAttribute("movingtab")) {
  303. clearTimeout(this.openTimer);
  304. clearTimeout(this.closeTimer);
  305. return this.closePopup();
  306. }
  307. if (this.triggerTab.matches(":hover"))
  308. this.navPopup.openPopup(this.triggerTab, {
  309. position: "after_start",
  310. triggerEvent: e,
  311. });
  312. }
  313. closePopup() {
  314. this.isOpen = false;
  315. this.navPopup.hidePopup(true);
  316. this.triggerTab = null;
  317. }
  318. // return true if the user's configured modifier key is pressed in the passed event.
  319. // return true if the user has the modifier key setting disabled.
  320. modifierPressed(e) {
  321. switch (this.config["Modifier key"]) {
  322. case undefined:
  323. case null:
  324. case false:
  325. case 0:
  326. return true;
  327. case "ctrl":
  328. return e.ctrlKey;
  329. case "shift":
  330. return e.shiftKey;
  331. case "alt":
  332. return e.altKey;
  333. case "meta":
  334. return e.metaKey;
  335. case "accel":
  336. return AppConstants.platform == "macosx" ? e.metaKey : e.ctrlKey;
  337. }
  338. }
  339. goBack(e) {
  340. if (!this.triggerTab) return;
  341. let { tabs } = this;
  342. let where = whereToOpenLink(e, false, true);
  343. if (where == "current")
  344. tabs.forEach((tab) => {
  345. let browser = gBrowser.getBrowserForTab(tab);
  346. if (browser.webNavigation?.canGoBack) browser.goBack();
  347. });
  348. else this.duplicateTabsIn(tabs, where, -1);
  349. }
  350. goForward(e) {
  351. if (!this.triggerTab) return;
  352. let { tabs } = this;
  353. let where = whereToOpenLink(e, false, true);
  354. if (where == "current")
  355. tabs.forEach((tab) => {
  356. let browser = gBrowser.getBrowserForTab(tab);
  357. if (browser.webNavigation?.canGoForward) browser.goForward();
  358. });
  359. else this.duplicateTabsIn(tabs, where, 1);
  360. }
  361. // used by the back/forward context menu items. navigates a given browser's history
  362. gotoHistoryIndex(e) {
  363. e = getRootEvent(e);
  364. let index = e.target.getAttribute("index");
  365. if (!index) return false;
  366. let where = whereToOpenLink(e);
  367. if (where == "current") {
  368. try {
  369. this.triggerTab.linkedBrowser.gotoIndex(index);
  370. } catch (ex) {
  371. return false;
  372. }
  373. return true;
  374. }
  375. let historyindex = e.target.getAttribute("historyindex");
  376. duplicateTabIn(this.triggerTab, where, Number(historyindex));
  377. return true;
  378. }
  379. // called when pressing the reload button. depending on modifier keys pressed,
  380. // either reload the tab in place or reload it in a new tab or window.
  381. reloadOrDuplicate(e) {
  382. e = getRootEvent(e);
  383. let { tabs } = this;
  384. let accelKeyPressed = AppConstants.platform == "macosx" ? e.metaKey : e.ctrlKey;
  385. let backgroundTabModifier = e.button == 1 || accelKeyPressed;
  386. if (e.shiftKey && !backgroundTabModifier) {
  387. this.browserReloadWithFlags(
  388. tabs,
  389. Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
  390. Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE
  391. );
  392. return;
  393. }
  394. let where = whereToOpenLink(e, false, true);
  395. if (where == "current")
  396. this.browserReloadWithFlags(tabs, Ci.nsIWebNavigation.LOAD_FLAGS_NONE);
  397. else this.duplicateTabsIn(tabs, where);
  398. }
  399. // this performs the same functions as above but does so after duplicating the tab as a new tab or in a new window
  400. duplicateTabsIn(tabs, where, i) {
  401. tabs.forEach((tab) => duplicateTabIn(tab, where, i));
  402. }
  403. // for a given set of tabs, reload their linked browsers with the passed (binary) flags.
  404. // we only use the bypass proxy & cache flags. this is the same as when you press ctrl+shift+R.
  405. browserReloadWithFlags(tabs, flags) {
  406. let unchangedRemoteness = [];
  407. tabs.forEach((tab) => {
  408. let browser = tab.linkedBrowser;
  409. let url = browser.currentURI.spec;
  410. let principal = tab.linkedBrowser.contentPrincipal;
  411. if (gBrowser.updateBrowserRemotenessByURL(browser, url)) {
  412. if (tab.linkedPanel) loadBrowserURI(browser, url, principal);
  413. else {
  414. tab.addEventListener(
  415. "SSTabRestoring",
  416. () => loadBrowserURI(browser, url, principal),
  417. { once: true }
  418. );
  419. gBrowser._insertBrowser(tab);
  420. }
  421. } else unchangedRemoteness.push(tab);
  422. });
  423. if (!unchangedRemoteness.length) return;
  424. for (let tab of unchangedRemoteness) {
  425. SitePermissions.clearTemporaryBlockPermissions(tab.linkedBrowser);
  426. delete tab.linkedBrowser.authPromptAbuseCounter;
  427. }
  428. gIdentityHandler.hidePopup();
  429. gPermissionPanel.hidePopup();
  430. let handlingUserInput = document.hasValidTransientUserGestureActivation;
  431. for (let tab of unchangedRemoteness) {
  432. if (tab.linkedPanel) sendReloadMessage(tab);
  433. else {
  434. tab.addEventListener("SSTabRestoring", () => sendReloadMessage(tab), {
  435. once: true,
  436. });
  437. gBrowser._insertBrowser(tab);
  438. }
  439. }
  440. function loadBrowserURI(browser, url, principal) {
  441. browser.loadURI(url, {
  442. flags,
  443. triggeringPrincipal: principal,
  444. });
  445. }
  446. function sendReloadMessage(tab) {
  447. tab.linkedBrowser.sendMessageToActor(
  448. "Browser:Reload",
  449. { flags, handlingUserInput },
  450. "BrowserTab"
  451. );
  452. }
  453. }
  454. // enable/disable the back and forward buttons according to whether the selected tabs can go back/forward
  455. updateButtonsState(tabs = this.tabs) {
  456. this.backButton.disabled = !tabs.some(
  457. (tab) => gBrowser.getBrowserForTab(tab).webNavigation?.canGoBack
  458. );
  459. this.forwardButton.disabled = !tabs.some(
  460. (tab) => gBrowser.getBrowserForTab(tab).webNavigation?.canGoForward
  461. );
  462. }
  463. // set the tooltip (tab title and url) in the nav popup to match the trigger tab
  464. handleTooltip() {
  465. let tab = this.triggerTab;
  466. if (!tab) return;
  467. let stringWithShortcut = (stringId, keyElemId, pluralCount) => {
  468. let keyElem = document.getElementById(keyElemId);
  469. let shortcut = ShortcutUtils.prettifyShortcut(keyElem);
  470. return PluralForm.get(pluralCount, gTabBrowserBundle.GetStringFromName(stringId))
  471. .replace("%S", shortcut)
  472. .replace("#1", pluralCount);
  473. };
  474. let label;
  475. const selectedTabs = gBrowser.selectedTabs;
  476. const contextTabInSelection = selectedTabs.includes(tab);
  477. const affectedTabsLength = contextTabInSelection ? selectedTabs.length : 1;
  478. this.setFavicon(tab);
  479. if (tab.mOverCloseButton) {
  480. let shortcut = ShortcutUtils.prettifyShortcut(key_close);
  481. label = PluralForm.get(
  482. affectedTabsLength,
  483. gTabBrowserBundle.GetStringFromName("tabs.closeTabs.tooltip")
  484. ).replace("#1", affectedTabsLength);
  485. if (contextTabInSelection && shortcut) {
  486. if (label.includes("%S")) label = label.replace("%S", shortcut);
  487. else label = label + " (" + shortcut + ")";
  488. }
  489. } else if (tab._overPlayingIcon) {
  490. let stringID;
  491. if (contextTabInSelection) {
  492. stringID = tab.linkedBrowser.audioMuted
  493. ? "tabs.unmuteAudio2.tooltip"
  494. : "tabs.muteAudio2.tooltip";
  495. label = stringWithShortcut(stringID, "key_toggleMute", affectedTabsLength);
  496. } else {
  497. if (tab.hasAttribute("activemedia-blocked")) {
  498. stringID = "tabs.unblockAudio2.tooltip";
  499. } else {
  500. stringID = tab.linkedBrowser.audioMuted
  501. ? "tabs.unmuteAudio2.background.tooltip"
  502. : "tabs.muteAudio2.background.tooltip";
  503. }
  504. label = PluralForm.get(
  505. affectedTabsLength,
  506. gTabBrowserBundle.GetStringFromName(stringID)
  507. ).replace("#1", affectedTabsLength);
  508. }
  509. } else label = gBrowser.getTabTooltip(tab);
  510. let title = this.navPopup.querySelector(".places-tooltip-title");
  511. title.value = label;
  512. let url = this.navPopup.querySelector(".places-tooltip-uri");
  513. url.value = tab.linkedBrowser?.currentURI?.spec.replace(/^https:\/\//, "");
  514. if (this.knownWidth) this.captureKnownWidth();
  515. }
  516. // sets the main favicon in the nav popup to match the trigger tab
  517. setFavicon(tab) {
  518. let busy = tab.getAttribute("busy");
  519. let progress = tab.getAttribute("progress");
  520. let { favicon } = this;
  521. this.setAttributes(favicon, {
  522. busy,
  523. progress,
  524. src: !busy && tab.getAttribute("image"),
  525. iconloadingprincipal: tab.getAttribute("iconloadingprincipal"),
  526. });
  527. if (busy) favicon.classList.add("tab-throbber-tabslist");
  528. else favicon.classList.remove("tab-throbber-tabslist");
  529. }
  530. // if the text in the popup becomes longer, it will make the popup smaller.
  531. // when the popup is anchored to the left, in LTR layout mode this doesn't matter,
  532. // because the popup will just extend to the right and the buttons on the left will remain in place.
  533. // but when the popup is anchored to the right, (e.g., a long tab title when the tab is to the right of the screen)
  534. // a change in dimensions will extend/retract from the left side.
  535. // that means if your mouse is on the back button and you click it, and the new page has a shorter title,
  536. // your mouse will fall outside the bounds of the popup and the popup will hide.
  537. // to prevent this, we cache the popup's width while it's anchored to the right
  538. // (or anchored to the left in RTL mode) to allow it to grow but never shrink while it's open.
  539. // its "known width" will reset when it closes, but while it remains open, it will never get smaller.
  540. // this is similar to how firefox's panel dimensions are sticky,
  541. // persisting even when you switch to a smaller subview, so buttons don't move around in a jerky way.
  542. captureKnownWidth() {
  543. let rect = windowUtils.getBoundsWithoutFlushing(this.tooltipBox);
  544. if (!rect) return;
  545. if (this.knownWidth && this.knownWidth > rect.width) return;
  546. this.knownWidth = rect.width;
  547. }
  548. // called when the back/forward context menu is open. fills it with navigation history entries.
  549. fillHistoryMenu(menupopup) {
  550. // if this setting is enabled, set up a listener for selection of items in the context menu.
  551. // this way when an item is "hovered" in the context menu, its title, url, and favicon
  552. // can be temporarily shown in the main nav popup, functioning like a tooltip or status bar.
  553. if (
  554. !menupopup.hasStatusListener &&
  555. this.config["Update tooltip when hovering in the history menu"]
  556. ) {
  557. menupopup.addEventListener("DOMMenuItemActive", (e) => {
  558. if (e.target.hasAttribute("checked")) this.handleTooltip();
  559. else {
  560. let uri = e.target.getAttribute("uri");
  561. let title = this.navPopup.querySelector(".places-tooltip-title");
  562. let urlLabel = this.navPopup.querySelector(".places-tooltip-uri");
  563. let { favicon } = this;
  564. title.value = e.target.getAttribute("label");
  565. urlLabel.value = uri.replace(/^https:\/\//, "");
  566. this.setAttributes(favicon, {
  567. busy: false,
  568. progress: false,
  569. src: `page-icon:${uri}`,
  570. });
  571. favicon.classList.remove("tab-throbber-tabslist");
  572. if (this.knownWidth) this.captureKnownWidth();
  573. }
  574. });
  575. menupopup.addEventListener("DOMMenuItemInactive", () => this.handleTooltip());
  576. menupopup.hasStatusListener = true;
  577. }
  578.  
  579. let children = menupopup.children;
  580. for (let i = children.length - 1; i >= 0; --i)
  581. if (children[i].hasAttribute("index")) menupopup.removeChild(children[i]);
  582.  
  583. const MAX_HISTORY_MENU_ITEMS = 15;
  584. const tooltipBack = gNavigatorBundle.getString("tabHistory.goBack");
  585. const tooltipCurrent = gNavigatorBundle.getString("tabHistory.current");
  586. const tooltipForward = gNavigatorBundle.getString("tabHistory.goForward");
  587.  
  588. function updateSessionHistory(sessionHistory, initial, ssInParent) {
  589. let count = ssInParent ? sessionHistory.count : sessionHistory.entries.length;
  590. if (!initial) {
  591. if (count <= 1) {
  592. menupopup.hidePopup();
  593. return;
  594. } else if (menupopup.id != "tabBackForwardMenu" && !menupopup.parentNode.open) {
  595. menupopup.parentNode.open = true;
  596. this.menuOpen = true;
  597. return;
  598. }
  599. }
  600. let { index } = sessionHistory;
  601. let half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2);
  602. let start = Math.max(index - half_length, 0);
  603. let end = Math.min(
  604. start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1,
  605. count
  606. );
  607. if (end == count) start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0);
  608. let existingIndex = 0;
  609. for (let j = end - 1; j >= start; j--) {
  610. let entry = ssInParent
  611. ? sessionHistory.getEntryAtIndex(j)
  612. : sessionHistory.entries[j];
  613. if (
  614. BrowserUtils.navigationRequireUserInteraction &&
  615. entry.hasUserInteraction === false &&
  616. j != end - 1 &&
  617. j != start
  618. )
  619. continue;
  620. let uri = ssInParent ? entry.URI.spec : entry.url;
  621. let item =
  622. existingIndex < children.length
  623. ? children[existingIndex]
  624. : document.createXULElement("menuitem");
  625. item.setAttribute("uri", uri);
  626. item.setAttribute("label", entry.title || uri);
  627. item.setAttribute("index", j);
  628. item.setAttribute("historyindex", j - index);
  629. if (j != index) item.style.listStyleImage = `url(page-icon:${uri})`;
  630. if (j < index) {
  631. item.className = "unified-nav-back menuitem-iconic menuitem-with-favicon";
  632. item.setAttribute("tooltiptext", tooltipBack);
  633. } else if (j == index) {
  634. item.setAttribute("type", "radio");
  635. item.setAttribute("checked", "true");
  636. item.className = "unified-nav-current";
  637. item.setAttribute("tooltiptext", tooltipCurrent);
  638. } else {
  639. item.className = "unified-nav-forward menuitem-iconic menuitem-with-favicon";
  640. item.setAttribute("tooltiptext", tooltipForward);
  641. }
  642. if (!item.parentNode) menupopup.appendChild(item);
  643. existingIndex++;
  644. }
  645. if (!initial) {
  646. let existingLength = children.length;
  647. while (existingIndex < existingLength) {
  648. menupopup.removeChild(menupopup.lastElementChild);
  649. existingIndex++;
  650. }
  651. }
  652. }
  653. if (this.triggerTab.multiselected) return false;
  654. let { browsingContext } = this.triggerTab.linkedBrowser;
  655. if (!browsingContext) return false;
  656. let { sessionHistory } = browsingContext;
  657. if (sessionHistory?.count) {
  658. if (sessionHistory.count <= 1) return false;
  659. updateSessionHistory(sessionHistory, true, true);
  660. } else {
  661. sessionHistory = SessionStore.getSessionHistory(this.triggerTab, updateSessionHistory);
  662. updateSessionHistory(sessionHistory, true, false);
  663. }
  664. this.menuOpen = true;
  665. return true;
  666. }
  667. registerSheet() {
  668. let css = `#tab-nav-popup {
  669. margin: 0;
  670. --arrowpanel-padding: 0;
  671. --panel-border-radius: var(--tooltip-border-radius, var(--arrowpanel-border-radius));
  672. }
  673. #tab-nav-popup-body {
  674. padding: var(--tab-nav-popup-padding, 2px 4px);
  675. }
  676. #tab-nav-popup .toolbarbutton-1 {
  677. appearance: none;
  678. margin: 0;
  679. padding: 0 var(--toolbarbutton-outer-padding);
  680. -moz-box-pack: center;
  681. background: none !important;
  682. outline: none !important;
  683. }
  684. #tab-nav-popup .toolbarbutton-1 > .toolbarbutton-icon {
  685. width: calc(2 * var(--toolbarbutton-inner-padding) + 16px);
  686. height: calc(2 * var(--toolbarbutton-inner-padding) + 16px);
  687. padding: var(--toolbarbutton-inner-padding);
  688. border-radius: var(--toolbarbutton-border-radius);
  689. }
  690. #tab-nav-popup
  691. .toolbarbutton-1:not([disabled="true"], [checked], [open], :active):hover
  692. > .toolbarbutton-icon {
  693. background-color: var(--toolbarbutton-hover-background);
  694. color: inherit;
  695. }
  696. #tab-nav-popup
  697. .toolbarbutton-1:not([disabled="true"]):is([open], [checked], :hover:active)
  698. > .toolbarbutton-icon {
  699. background-color: var(--toolbarbutton-active-background);
  700. color: inherit;
  701. }
  702. #tab-nav-popup .toolbarbutton-1:-moz-focusring > .toolbarbutton-icon {
  703. color: inherit;
  704. outline: var(--toolbarbutton-focus-outline);
  705. outline-offset: calc(var(--focus-outline-width) * -1);
  706. }
  707. :root[uidensity="compact"]
  708. #tab-nav-popup
  709. .toolbarbutton-1:-moz-focusring
  710. > .toolbarbutton-icon {
  711. outline-offset: calc(var(--focus-outline-width) * -1 - 1px);
  712. }
  713. #tab-nav-separator {
  714. border-left: 1px solid var(--panel-separator-color);
  715. width: 0;
  716. margin-block: 3px;
  717. margin-inline: 4px 6px;
  718. }
  719. #tab-nav-tooltip-box {
  720. min-width: var(--tab-nav-known-width, revert);
  721. }
  722. #tab-nav-tooltip-textbox {
  723. padding-block: 4px;
  724. border: 0;
  725. }
  726. #tab-nav-tooltip-favicon {
  727. list-style-image: url("chrome://global/skin/icons/defaultFavicon.svg");
  728. width: 16px;
  729. height: 16px;
  730. margin-inline: 2px;
  731. -moz-context-properties: fill;
  732. fill: currentColor;
  733. }
  734. #tab-nav-back {
  735. list-style-image: url("chrome://browser/skin/back.svg");
  736. }
  737. #tab-nav-forward {
  738. list-style-image: url("chrome://browser/skin/forward.svg");
  739. }
  740. #tab-nav-reload {
  741. list-style-image: url("chrome://global/skin/icons/reload.svg");
  742. }
  743. #tab-nav-back:-moz-locale-dir(rtl) > .toolbarbutton-icon,
  744. #tab-nav-forward:-moz-locale-dir(rtl) > .toolbarbutton-icon,
  745. #tab-nav-reload:-moz-locale-dir(rtl) > .toolbarbutton-icon {
  746. scale: -1 1;
  747. }
  748. .tabbrowser-tab[open] > .tab-stack > .tab-background:not([selected="true"], [multiselected]) {
  749. background-color: color-mix(in srgb, currentColor 11%, transparent);
  750. }
  751. #tab-nav-popup[side]::part(arrowbox) {
  752. display: none;
  753. }
  754. #tab-nav-popup[type="arrow"]::part(content) {
  755. margin: 0;
  756. }
  757. `;
  758. let sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(
  759. Ci.nsIStyleSheetService
  760. );
  761. let uri = makeURI("data:text/css;charset=UTF=8," + encodeURIComponent(css));
  762. if (sss.sheetRegistered(uri, sss.AUTHOR_SHEET)) return; // avoid loading duplicate sheets on subsequent window launches.
  763. sss.loadAndRegisterSheet(uri, sss.AUTHOR_SHEET);
  764. }
  765. }
  766.  
  767. if (gBrowserInit.delayedStartupFinished) window.tabNavButtons = new TabTooltipNav();
  768. else {
  769. let delayedListener = (subject, topic) => {
  770. if (topic == "browser-delayed-startup-finished" && subject == window) {
  771. Services.obs.removeObserver(delayedListener, topic);
  772. window.tabNavButtons = new TabTooltipNav();
  773. }
  774. };
  775. Services.obs.addObserver(delayedListener, "browser-delayed-startup-finished");
  776. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement