Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
// eslint-disable-next-line import/no-unassigned-import
// eslint-disable-next-line import/no-unassigned-import
// eslint-disable-next-line import/no-unassigned-import
// eslint-disable-next-line import/no-unassigned-import
import { placeLinkOnClipboard } from "./helpers.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
});
const WIN_RESIZE_DEBOUNCE_RATE_MS = 500;
const WIN_RESIZE_DEBOUNCE_TIMEOUT_MS = 1000;
/**
* A base class for content container views displayed on firefox-view.
*
* @property {boolean} recentBrowsing
* Is part of the recentbrowsing page view
* @property {boolean} paused
* No content will be updated and rendered while paused
*/
export class ViewPageContent extends MozLitElement {
static get properties() {
return {
recentBrowsing: { type: Boolean },
paused: { type: Boolean },
};
}
constructor() {
super();
// don't update or render until explicitly un-paused
this.paused = true;
}
get ownerViewPage() {
return this.closest("[type='page']") || this;
}
get isVisible() {
if (!this.isConnected || this.ownerDocument.visibilityState != "visible") {
return false;
}
return this.ownerViewPage.selectedTab;
}
/**
* Override this function to run a callback whenever this content is visible.
*/
viewVisibleCallback() {}
/**
* Override this function to run a callback whenever this content is hidden.
*/
viewHiddenCallback() {}
getWindow() {
return window.browsingContext.embedderWindowGlobal.browsingContext.window;
}
get isSelectedBrowserTab() {
const { gBrowser } = this.getWindow();
return gBrowser.selectedBrowser.browsingContext == window.browsingContext;
}
copyLink(e) {
placeLinkOnClipboard(this.triggerNode.title, this.triggerNode.url);
this.recordContextMenuTelemetry("copy-link", e);
}
openInNewWindow(e) {
this.getWindow().openTrustedLinkIn(this.triggerNode.url, "window", {
private: false,
});
this.recordContextMenuTelemetry("open-in-new-window", e);
}
openInNewPrivateWindow(e) {
this.getWindow().openTrustedLinkIn(this.triggerNode.url, "window", {
private: true,
});
this.recordContextMenuTelemetry("open-in-private-window", e);
}
recordContextMenuTelemetry(menuAction, event) {
Services.telemetry.recordEvent(
"firefoxview_next",
"context_menu",
"tabs",
null,
{
menu_action: menuAction,
data_type: event.target.panel.dataset.tabType,
}
);
}
shouldUpdate(changedProperties) {
return !this.paused && super.shouldUpdate(changedProperties);
}
}
/**
* A "page" in firefox view, which may be hidden or shown by the named-deck container or
* via the owner document's visibility
*
* @property {boolean} selectedTab
* Is this page the selected view in the named-deck container
*/
export class ViewPage extends ViewPageContent {
static get properties() {
return {
selectedTab: { type: Boolean },
searchTextboxSize: { type: Number },
};
}
constructor() {
super();
this.selectedTab = false;
this.recentBrowsing = Boolean(this.recentBrowsingElement);
this.onTabSelect = this.onTabSelect.bind(this);
this.onResize = this.onResize.bind(this);
}
get recentBrowsingElement() {
return this.closest("VIEW-RECENTBROWSING");
}
onResize() {
this.windowResizeTask = new lazy.DeferredTask(
() => this.updateAllVirtualLists(),
WIN_RESIZE_DEBOUNCE_RATE_MS,
WIN_RESIZE_DEBOUNCE_TIMEOUT_MS
);
this.windowResizeTask?.arm();
}
onTabSelect({ target }) {
const win = target.ownerGlobal;
let selfBrowser = window.docShell?.chromeEventHandler;
const { gBrowser } = this.getWindow();
let isForegroundTab = gBrowser.selectedBrowser == selfBrowser;
if (win.FirefoxViewHandler.tab?.selected && isForegroundTab) {
this.paused = false;
this.viewVisibleCallback();
} else {
this.paused = true;
this.viewHiddenCallback();
}
}
connectedCallback() {
super.connectedCallback();
}
disconnectedCallback() {
super.disconnectedCallback();
this.getWindow().removeEventListener("resize", this.onResize);
this.getWindow().removeEventListener("TabSelect", this.onTabSelect);
}
updateAllVirtualLists() {
if (!this.paused) {
let tabLists = [];
if (this.recentBrowsing) {
let viewComponents = this.querySelectorAll("[slot]");
viewComponents.forEach(viewComponent => {
let currentTabLists = [];
if (viewComponent.nodeName.includes("OPENTABS")) {
viewComponent.viewCards.forEach(viewCard => {
currentTabLists.push(viewCard.tabList);
});
} else {
currentTabLists =
viewComponent.shadowRoot.querySelectorAll("fxview-tab-list");
}
tabLists.push(...currentTabLists);
});
} else {
tabLists = this.shadowRoot.querySelectorAll("fxview-tab-list");
}
tabLists.forEach(tabList => {
if (!tabList.updatesPaused && tabList.rootVirtualListEl?.isVisible) {
tabList.rootVirtualListEl.recalculateAfterWindowResize();
}
});
}
}
toggleVisibilityInCardContainer(isOpenTabs) {
let cards = [];
let tabLists = [];
if (!isOpenTabs) {
cards = this.shadowRoot.querySelectorAll("card-container");
tabLists = this.shadowRoot.querySelectorAll("fxview-tab-list");
} else {
this.viewCards.forEach(viewCard => {
if (viewCard.cardEl) {
cards.push(viewCard.cardEl);
tabLists.push(viewCard.tabList);
}
});
}
if (tabLists.length && cards.length) {
cards.forEach(cardEl => {
if (cardEl.visible !== !this.paused) {
cardEl.visible = !this.paused;
} else if (
cardEl.isExpanded &&
Array.from(tabLists).some(
tabList => tabList.updatesPaused !== this.paused
)
) {
// If card is already visible and expanded but tab-list has updatesPaused,
// update the tab-list updatesPaused prop from here instead of card-container
tabLists.forEach(tabList => {
tabList.updatesPaused = this.paused;
});
}
});
}
}
enter() {
this.selectedTab = true;
if (this.isVisible) {
this.paused = false;
this.viewVisibleCallback();
this.getWindow().addEventListener("resize", this.onResize);
this.getWindow().addEventListener("TabSelect", this.onTabSelect);
}
}
exit() {
this.selectedTab = false;
this.paused = true;
this.viewHiddenCallback();
if (!this.windowResizeTask?.isFinalized) {
this.windowResizeTask?.finalize();
}
this.getWindow().removeEventListener("resize", this.onResize);
this.getWindow().removeEventListener("TabSelect", this.onTabSelect);
}
}