summaryrefslogtreecommitdiffstats
path: root/browser/components/tabpreview/tabpreview.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/tabpreview/tabpreview.mjs')
-rw-r--r--browser/components/tabpreview/tabpreview.mjs249
1 files changed, 249 insertions, 0 deletions
diff --git a/browser/components/tabpreview/tabpreview.mjs b/browser/components/tabpreview/tabpreview.mjs
new file mode 100644
index 0000000000..5256ab22ff
--- /dev/null
+++ b/browser/components/tabpreview/tabpreview.mjs
@@ -0,0 +1,249 @@
+/* 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);