/* 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 { html } from "chrome://global/content/vendor/lit.all.mjs"; import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; var { XPCOMUtils } = ChromeUtils.importESModule( "resource://gre/modules/XPCOMUtils.sys.mjs" ); const TAB_PREVIEW_USE_THUMBNAILS_PREF = "browser.tabs.cardPreview.showThumbnails"; const TAB_PREVIEW_DELAY_PREF = "browser.tabs.cardPreview.delayMs"; /** * Detailed preview card that displays when hovering a tab * * @property {MozTabbrowserTab} tab - the tab to preview * @fires TabPreview#previewhidden * @fires TabPreview#previewshown * @fires TabPreview#previewThumbnailUpdated */ export default class TabPreview extends MozLitElement { static properties = { tab: { type: Object }, _previewIsActive: { type: Boolean, state: true }, _previewDelayTimeout: { type: Number, state: true }, _displayTitle: { type: String, state: true }, _displayURI: { type: String, state: true }, _displayImg: { type: Object, state: true }, }; constructor() { super(); XPCOMUtils.defineLazyPreferenceGetter( this, "_prefPreviewDelay", TAB_PREVIEW_DELAY_PREF, 1000 ); XPCOMUtils.defineLazyPreferenceGetter( this, "_prefDisplayThumbnail", TAB_PREVIEW_USE_THUMBNAILS_PREF, false ); } // render this inside a createRenderRoot() { if (!document.createXULElement) { console.error( "Unable to create panel: document.createXULElement is not available" ); return super.createRenderRoot(); } this.attachShadow({ mode: "open" }); this.panel = document.createXULElement("panel"); this.panel.setAttribute("id", "tabPreviewPanel"); this.panel.setAttribute("noautofocus", true); this.panel.setAttribute("norolluponanchor", true); this.panel.setAttribute("consumeoutsideclicks", "never"); this.panel.setAttribute("level", "parent"); this.panel.setAttribute("type", "arrow"); this.shadowRoot.append(this.panel); return this.panel; } get previewCanShow() { return this._previewIsActive && this.tab; } get thumbnailCanShow() { return ( this.previewCanShow && this._prefDisplayThumbnail && !this.tab.selected && this._displayImg ); } getPrettyURI(uri) { try { const url = new URL(uri); return `${url.hostname}${url.pathname}`.replace(/\/+$/, ""); } catch { return this.pageURI; } } handleEvent(e) { switch (e.type) { case "TabSelect": { this.requestUpdate(); break; } case "wheel": { this.hidePreview(); break; } case "popuphidden": { this.previewHidden(); break; } } } showPreview() { this.panel.openPopup(this.tab, { position: "bottomleft topleft", y: -2, isContextMenu: false, }); window.addEventListener("wheel", this, { capture: true, passive: true, }); window.addEventListener("TabSelect", this); this.panel.addEventListener("popuphidden", this); } hidePreview() { this.panel.hidePopup(); this.updateComplete.then(() => { /** * @event TabPreview#previewhidden * @type {CustomEvent} */ this.dispatchEvent(new CustomEvent("previewhidden")); }); } previewHidden() { window.removeEventListener("wheel", this, { capture: true, passive: true }); window.removeEventListener("TabSelect", this); this.panel.removeEventListener("popuphidden", this); } // compute values derived from tab element willUpdate(changedProperties) { if (!changedProperties.has("tab")) { return; } if (!this.tab) { this._displayTitle = ""; this._displayURI = ""; this._displayImg = null; return; } this._displayTitle = this.tab.textLabel.textContent; this._displayURI = this.getPrettyURI( this.tab.linkedBrowser.currentURI.spec ); this._displayImg = null; let { tab } = this; window.tabPreviews.get(this.tab).then(el => { if (this.tab == tab) { this._displayImg = el; } }); } updated(changedProperties) { if (changedProperties.has("tab")) { // handle preview delay if (!this.tab) { clearTimeout(this._previewDelayTimeout); this._previewIsActive = false; } else { let lastTabVal = changedProperties.get("tab"); if (!lastTabVal) { // tab was set from an empty state, // so wait for the delay duration before showing this._previewDelayTimeout = setTimeout(() => { this._previewIsActive = true; }, this._prefPreviewDelay); } } } if (changedProperties.has("_previewIsActive")) { if (!this._previewIsActive) { this.hidePreview(); } } if ( (changedProperties.has("tab") || changedProperties.has("_previewIsActive")) && this.previewCanShow ) { this.updateComplete.then(() => { if (this.panel.state == "open" || this.panel.state == "showing") { this.panel.moveToAnchor(this.tab, "bottomleft topleft", 0, -2); } else { this.showPreview(); } this.dispatchEvent( /** * @event TabPreview#previewshown * @type {CustomEvent} * @property {object} detail * @property {MozTabbrowserTab} detail.tab - the tab being previewed */ new CustomEvent("previewshown", { detail: { tab: this.tab }, }) ); }); } if (changedProperties.has("_displayImg")) { this.updateComplete.then(() => { /** * fires when the thumbnail for a preview is loaded * and added to the document. * * @event TabPreview#previewThumbnailUpdated * @type {CustomEvent} */ this.dispatchEvent(new CustomEvent("previewThumbnailUpdated")); }); } } render() { return html`
${this._displayTitle}
${this._displayURI}
${this.thumbnailCanShow ? html`
${this._displayImg}
` : ""}
`; } } customElements.define("tab-preview", TabPreview);