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