Bug 1944416: Restore individual tabs from closed groups in closed windows r=dao,sessi...
[gecko.git] / browser / components / firefoxview / viewpage.mjs
blobeba7a793ba10d7b0573c1f2b118a5642e9a701c7
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 { MozLitElement } from "chrome://global/content/lit-utils.mjs";
7 // eslint-disable-next-line import/no-unassigned-import
8 import "chrome://browser/content/firefoxview/card-container.mjs";
9 // eslint-disable-next-line import/no-unassigned-import
10 import "chrome://browser/content/firefoxview/fxview-empty-state.mjs";
11 // eslint-disable-next-line import/no-unassigned-import
12 import "chrome://browser/content/firefoxview/fxview-search-textbox.mjs";
13 // eslint-disable-next-line import/no-unassigned-import
14 import "chrome://browser/content/firefoxview/fxview-tab-list.mjs";
16 import { placeLinkOnClipboard } from "./helpers.mjs";
18 const lazy = {};
20 ChromeUtils.defineESModuleGetters(lazy, {
21   DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
22 });
24 const WIN_RESIZE_DEBOUNCE_RATE_MS = 500;
25 const WIN_RESIZE_DEBOUNCE_TIMEOUT_MS = 1000;
27 /**
28  * A base class for content container views displayed on firefox-view.
29  *
30  * @property {boolean} recentBrowsing
31  *   Is part of the recentbrowsing page view
32  * @property {boolean} paused
33  *   No content will be updated and rendered while paused
34  */
35 export class ViewPageContent extends MozLitElement {
36   static get properties() {
37     return {
38       recentBrowsing: { type: Boolean },
39       paused: { type: Boolean },
40     };
41   }
42   constructor() {
43     super();
44     // don't update or render until explicitly un-paused
45     this.paused = true;
46   }
48   get ownerViewPage() {
49     return this.closest("[type='page']") || this;
50   }
52   get isVisible() {
53     if (!this.isConnected || this.ownerDocument.visibilityState != "visible") {
54       return false;
55     }
56     return this.ownerViewPage.selectedTab;
57   }
59   /**
60    * Override this function to run a callback whenever this content is visible.
61    */
62   viewVisibleCallback() {}
64   /**
65    * Override this function to run a callback whenever this content is hidden.
66    */
67   viewHiddenCallback() {}
69   getWindow() {
70     return window.browsingContext.embedderWindowGlobal.browsingContext.window;
71   }
73   get isSelectedBrowserTab() {
74     const { gBrowser } = this.getWindow();
75     return gBrowser.selectedBrowser.browsingContext == window.browsingContext;
76   }
78   copyLink(e) {
79     placeLinkOnClipboard(this.triggerNode.title, this.triggerNode.url);
80     this.recordContextMenuTelemetry("copy-link", e);
81   }
83   openInNewWindow(e) {
84     this.getWindow().openTrustedLinkIn(this.triggerNode.url, "window", {
85       private: false,
86     });
87     this.recordContextMenuTelemetry("open-in-new-window", e);
88   }
90   openInNewPrivateWindow(e) {
91     this.getWindow().openTrustedLinkIn(this.triggerNode.url, "window", {
92       private: true,
93     });
94     this.recordContextMenuTelemetry("open-in-private-window", e);
95   }
97   recordContextMenuTelemetry(menuAction, event) {
98     Glean.firefoxviewNext.contextMenuTabs.record({
99       menu_action: menuAction,
100       data_type: event.target.panel.dataset.tabType,
101     });
102   }
104   shouldUpdate(changedProperties) {
105     return !this.paused && super.shouldUpdate(changedProperties);
106   }
110  * A "page" in firefox view, which may be hidden or shown by the named-deck container or
111  * via the owner document's visibility
113  * @property {boolean} selectedTab
114  *   Is this page the selected view in the named-deck container
115  */
116 export class ViewPage extends ViewPageContent {
117   static get properties() {
118     return {
119       selectedTab: { type: Boolean },
120       searchTextboxSize: { type: Number },
121     };
122   }
124   constructor() {
125     super();
126     this.selectedTab = false;
127     this.recentBrowsing = Boolean(this.recentBrowsingElement);
128     this.onTabSelect = this.onTabSelect.bind(this);
129     this.onResize = this.onResize.bind(this);
130   }
132   get recentBrowsingElement() {
133     return this.closest("VIEW-RECENTBROWSING");
134   }
136   onResize() {
137     this.windowResizeTask = new lazy.DeferredTask(
138       () => this.updateAllVirtualLists(),
139       WIN_RESIZE_DEBOUNCE_RATE_MS,
140       WIN_RESIZE_DEBOUNCE_TIMEOUT_MS
141     );
142     this.windowResizeTask?.arm();
143   }
145   onTabSelect({ target }) {
146     const win = target.ownerGlobal;
148     let selfBrowser = window.docShell?.chromeEventHandler;
149     const { gBrowser } = this.getWindow();
150     let isForegroundTab = gBrowser.selectedBrowser == selfBrowser;
152     if (win.FirefoxViewHandler.tab?.selected && isForegroundTab) {
153       this.paused = false;
154       this.viewVisibleCallback();
155     } else {
156       this.paused = true;
157       this.viewHiddenCallback();
158     }
159   }
161   connectedCallback() {
162     super.connectedCallback();
163   }
165   disconnectedCallback() {
166     super.disconnectedCallback();
167     this.getWindow().removeEventListener("resize", this.onResize);
168     this.getWindow().removeEventListener("TabSelect", this.onTabSelect);
169   }
171   updateAllVirtualLists() {
172     if (!this.paused) {
173       let tabLists = [];
174       if (this.recentBrowsing) {
175         let viewComponents = this.querySelectorAll("[slot]");
176         viewComponents.forEach(viewComponent => {
177           let currentTabLists = [];
178           if (viewComponent.nodeName.includes("OPENTABS")) {
179             viewComponent.viewCards.forEach(viewCard => {
180               currentTabLists.push(viewCard.tabList);
181             });
182           } else {
183             currentTabLists =
184               viewComponent.shadowRoot.querySelectorAll("fxview-tab-list");
185           }
186           tabLists.push(...currentTabLists);
187         });
188       } else {
189         tabLists = this.shadowRoot.querySelectorAll("fxview-tab-list");
190       }
191       tabLists.forEach(tabList => {
192         if (!tabList.updatesPaused && tabList.rootVirtualListEl?.isVisible) {
193           tabList.rootVirtualListEl.recalculateAfterWindowResize();
194         }
195       });
196     }
197   }
199   toggleVisibilityInCardContainer(isOpenTabs) {
200     let cards = [];
201     let tabLists = [];
202     if (!isOpenTabs) {
203       cards = this.shadowRoot.querySelectorAll("card-container");
204       tabLists = this.shadowRoot.querySelectorAll(
205         "fxview-tab-list, syncedtabs-tab-list"
206       );
207     } else {
208       this.viewCards.forEach(viewCard => {
209         if (viewCard.cardEl) {
210           cards.push(viewCard.cardEl);
211           tabLists.push(viewCard.tabList);
212         }
213       });
214     }
215     if (tabLists.length && cards.length) {
216       cards.forEach(cardEl => {
217         if (cardEl.visible !== !this.paused) {
218           cardEl.visible = !this.paused;
219         } else if (
220           cardEl.isExpanded &&
221           Array.from(tabLists).some(
222             tabList => tabList.updatesPaused !== this.paused
223           )
224         ) {
225           // If card is already visible and expanded but tab-list has updatesPaused,
226           // update the tab-list updatesPaused prop from here instead of card-container
227           tabLists.forEach(tabList => {
228             tabList.updatesPaused = this.paused;
229           });
230         }
231       });
232     }
233   }
235   enter() {
236     this.selectedTab = true;
237     if (this.isVisible) {
238       this.paused = false;
239       this.viewVisibleCallback();
240       this.getWindow().addEventListener("resize", this.onResize);
241       this.getWindow().addEventListener("TabSelect", this.onTabSelect);
242     }
243   }
245   exit() {
246     this.selectedTab = false;
247     this.paused = true;
248     this.viewHiddenCallback();
249     if (!this.windowResizeTask?.isFinalized) {
250       this.windowResizeTask?.finalize();
251     }
252     this.getWindow().removeEventListener("resize", this.onResize);
253     this.getWindow().removeEventListener("TabSelect", this.onTabSelect);
254   }