fune/browser/components/tabpreview/tabpreview.mjs
2024-01-24 19:20:38 +00:00

249 lines
6.7 KiB
JavaScript

/* 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 <panel>
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`
<link
rel="stylesheet"
type="text/css"
href="chrome://browser/content/tabpreview/tabpreview.css"
/>
<div class="tab-preview-container">
<div class="tab-preview-text-container">
<div class="tab-preview-title">${this._displayTitle}</div>
<div class="tab-preview-uri">${this._displayURI}</div>
</div>
${this.thumbnailCanShow
? html`
<div class="tab-preview-thumbnail-container">
${this._displayImg}
</div>
`
: ""}
</div>
`;
}
}
customElements.define("tab-preview", TabPreview);