Backed out changeset aa5fc411278d (bug 1876522) for causing failures on browser_tab_t...
[gecko.git] / browser / components / tabpreview / tabpreview.mjs
blob5256ab22ff3a79d9c020dac2ce375f5e42f9c13c
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/. */
5 import { html } from "chrome://global/content/vendor/lit.all.mjs";
6 import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
8 var { XPCOMUtils } = ChromeUtils.importESModule(
9   "resource://gre/modules/XPCOMUtils.sys.mjs"
12 const TAB_PREVIEW_USE_THUMBNAILS_PREF =
13   "browser.tabs.cardPreview.showThumbnails";
14 const TAB_PREVIEW_DELAY_PREF = "browser.tabs.cardPreview.delayMs";
16 /**
17  * Detailed preview card that displays when hovering a tab
18  *
19  * @property {MozTabbrowserTab} tab - the tab to preview
20  * @fires TabPreview#previewhidden
21  * @fires TabPreview#previewshown
22  * @fires TabPreview#previewThumbnailUpdated
23  */
24 export default class TabPreview extends MozLitElement {
25   static properties = {
26     tab: { type: Object },
28     _previewIsActive: { type: Boolean, state: true },
29     _previewDelayTimeout: { type: Number, state: true },
30     _displayTitle: { type: String, state: true },
31     _displayURI: { type: String, state: true },
32     _displayImg: { type: Object, state: true },
33   };
35   constructor() {
36     super();
37     XPCOMUtils.defineLazyPreferenceGetter(
38       this,
39       "_prefPreviewDelay",
40       TAB_PREVIEW_DELAY_PREF,
41       1000
42     );
43     XPCOMUtils.defineLazyPreferenceGetter(
44       this,
45       "_prefDisplayThumbnail",
46       TAB_PREVIEW_USE_THUMBNAILS_PREF,
47       false
48     );
49   }
51   // render this inside a <panel>
52   createRenderRoot() {
53     if (!document.createXULElement) {
54       console.error(
55         "Unable to create panel: document.createXULElement is not available"
56       );
57       return super.createRenderRoot();
58     }
59     this.attachShadow({ mode: "open" });
60     this.panel = document.createXULElement("panel");
61     this.panel.setAttribute("id", "tabPreviewPanel");
62     this.panel.setAttribute("noautofocus", true);
63     this.panel.setAttribute("norolluponanchor", true);
64     this.panel.setAttribute("consumeoutsideclicks", "never");
65     this.panel.setAttribute("level", "parent");
66     this.panel.setAttribute("type", "arrow");
67     this.shadowRoot.append(this.panel);
68     return this.panel;
69   }
71   get previewCanShow() {
72     return this._previewIsActive && this.tab;
73   }
75   get thumbnailCanShow() {
76     return (
77       this.previewCanShow &&
78       this._prefDisplayThumbnail &&
79       !this.tab.selected &&
80       this._displayImg
81     );
82   }
84   getPrettyURI(uri) {
85     try {
86       const url = new URL(uri);
87       return `${url.hostname}${url.pathname}`.replace(/\/+$/, "");
88     } catch {
89       return this.pageURI;
90     }
91   }
93   handleEvent(e) {
94     switch (e.type) {
95       case "TabSelect": {
96         this.requestUpdate();
97         break;
98       }
99       case "wheel": {
100         this.hidePreview();
101         break;
102       }
103       case "popuphidden": {
104         this.previewHidden();
105         break;
106       }
107     }
108   }
110   showPreview() {
111     this.panel.openPopup(this.tab, {
112       position: "bottomleft topleft",
113       y: -2,
114       isContextMenu: false,
115     });
116     window.addEventListener("wheel", this, {
117       capture: true,
118       passive: true,
119     });
120     window.addEventListener("TabSelect", this);
121     this.panel.addEventListener("popuphidden", this);
122   }
124   hidePreview() {
125     this.panel.hidePopup();
126     this.updateComplete.then(() => {
127       /**
128        * @event TabPreview#previewhidden
129        * @type {CustomEvent}
130        */
131       this.dispatchEvent(new CustomEvent("previewhidden"));
132     });
133   }
135   previewHidden() {
136     window.removeEventListener("wheel", this, { capture: true, passive: true });
137     window.removeEventListener("TabSelect", this);
138     this.panel.removeEventListener("popuphidden", this);
139   }
141   // compute values derived from tab element
142   willUpdate(changedProperties) {
143     if (!changedProperties.has("tab")) {
144       return;
145     }
146     if (!this.tab) {
147       this._displayTitle = "";
148       this._displayURI = "";
149       this._displayImg = null;
150       return;
151     }
152     this._displayTitle = this.tab.textLabel.textContent;
153     this._displayURI = this.getPrettyURI(
154       this.tab.linkedBrowser.currentURI.spec
155     );
156     this._displayImg = null;
157     let { tab } = this;
158     window.tabPreviews.get(this.tab).then(el => {
159       if (this.tab == tab) {
160         this._displayImg = el;
161       }
162     });
163   }
165   updated(changedProperties) {
166     if (changedProperties.has("tab")) {
167       // handle preview delay
168       if (!this.tab) {
169         clearTimeout(this._previewDelayTimeout);
170         this._previewIsActive = false;
171       } else {
172         let lastTabVal = changedProperties.get("tab");
173         if (!lastTabVal) {
174           // tab was set from an empty state,
175           // so wait for the delay duration before showing
176           this._previewDelayTimeout = setTimeout(() => {
177             this._previewIsActive = true;
178           }, this._prefPreviewDelay);
179         }
180       }
181     }
182     if (changedProperties.has("_previewIsActive")) {
183       if (!this._previewIsActive) {
184         this.hidePreview();
185       }
186     }
187     if (
188       (changedProperties.has("tab") ||
189         changedProperties.has("_previewIsActive")) &&
190       this.previewCanShow
191     ) {
192       this.updateComplete.then(() => {
193         if (this.panel.state == "open" || this.panel.state == "showing") {
194           this.panel.moveToAnchor(this.tab, "bottomleft topleft", 0, -2);
195         } else {
196           this.showPreview();
197         }
199         this.dispatchEvent(
200           /**
201            * @event TabPreview#previewshown
202            * @type {CustomEvent}
203            * @property {object} detail
204            * @property {MozTabbrowserTab} detail.tab - the tab being previewed
205            */
206           new CustomEvent("previewshown", {
207             detail: { tab: this.tab },
208           })
209         );
210       });
211     }
212     if (changedProperties.has("_displayImg")) {
213       this.updateComplete.then(() => {
214         /**
215          * fires when the thumbnail for a preview is loaded
216          * and added to the document.
217          *
218          * @event TabPreview#previewThumbnailUpdated
219          * @type {CustomEvent}
220          */
221         this.dispatchEvent(new CustomEvent("previewThumbnailUpdated"));
222       });
223     }
224   }
226   render() {
227     return html`
228       <link
229         rel="stylesheet"
230         type="text/css"
231         href="chrome://browser/content/tabpreview/tabpreview.css"
232       />
233       <div class="tab-preview-container">
234         <div class="tab-preview-text-container">
235           <div class="tab-preview-title">${this._displayTitle}</div>
236           <div class="tab-preview-uri">${this._displayURI}</div>
237         </div>
238         ${this.thumbnailCanShow
239           ? html`
240               <div class="tab-preview-thumbnail-container">
241                 ${this._displayImg}
242               </div>
243             `
244           : ""}
245       </div>
246     `;
247   }
249 customElements.define("tab-preview", TabPreview);