|
12 | 12 | window.addEventListener('TabClose', this.onTabClose.bind(this));
|
13 | 13 | window.addEventListener('TabSelect', this.onLocationChange.bind(this));
|
14 | 14 |
|
| 15 | + ChromeUtils.defineLazyGetter(this, 'sidebarButtons', () => document.getElementById('zen-glance-sidebar-container')); |
| 16 | + document.getElementById('tabbrowser-tabpanels').addEventListener('click', this.onOverlayClick.bind(this)); |
| 17 | + Services.obs.addObserver(this, 'quit-application-requested'); |
| 18 | + |
15 | 19 | XPCOMUtils.defineLazyPreferenceGetter(
|
16 | 20 | this._lazyPref,
|
17 | 21 | 'SHOULD_OPEN_EXTERNAL_TABS_IN_GLANCE',
|
18 | 22 | 'zen.glance.open-essential-external-links',
|
19 | 23 | false
|
20 | 24 | );
|
21 |
| - |
22 |
| - ChromeUtils.defineLazyGetter(this, 'sidebarButtons', () => document.getElementById('zen-glance-sidebar-container')); |
23 |
| - |
24 |
| - document.getElementById('tabbrowser-tabpanels').addEventListener('click', this.onOverlayClick.bind(this)); |
25 |
| - |
26 |
| - Services.obs.addObserver(this, 'quit-application-requested'); |
27 | 25 | }
|
28 | 26 |
|
29 | 27 | get #currentBrowser() {
|
30 | 28 | return this.#glances.get(this.#currentGlanceID)?.browser;
|
31 | 29 | }
|
32 |
| - |
33 | 30 | get #currentTab() {
|
34 | 31 | return this.#glances.get(this.#currentGlanceID)?.tab;
|
35 | 32 | }
|
36 |
| - |
37 | 33 | get #currentParentTab() {
|
38 | 34 | return this.#glances.get(this.#currentGlanceID)?.parentTab;
|
39 | 35 | }
|
40 | 36 |
|
41 | 37 | onOverlayClick(event) {
|
| 38 | + // If user clicks outside content area, close glance |
42 | 39 | if (event.target === this.overlay && event.originalTarget !== this.contentWrapper) {
|
43 | 40 | this.closeGlance({ onTabClose: true });
|
44 | 41 | }
|
|
53 | 50 | }
|
54 | 51 |
|
55 | 52 | onUnload() {
|
56 |
| - // clear everything |
| 53 | + // Clean up all open Glances |
57 | 54 | for (let [id, glance] of this.#glances) {
|
58 | 55 | gBrowser.removeTab(glance.tab, { animate: false });
|
59 | 56 | }
|
60 | 57 | }
|
61 | 58 |
|
| 59 | + // Figure out where to insert new tabs |
62 | 60 | getTabPosition(tab) {
|
63 | 61 | return Math.max(gBrowser.pinnedTabCount, tab._tPos);
|
64 | 62 | }
|
65 | 63 |
|
| 64 | + // Create a new tab for Glance |
66 | 65 | createBrowserElement(url, currentTab, existingTab = null) {
|
67 | 66 | const newTabOptions = {
|
68 | 67 | userContextId: currentTab.getAttribute('usercontextid') || '',
|
|
72 | 71 | index: this.getTabPosition(currentTab) + 1,
|
73 | 72 | };
|
74 | 73 | currentTab._selected = true;
|
| 74 | + |
75 | 75 | const newUUID = gZenUIManager.generateUuidv4();
|
76 | 76 | const newTab = existingTab ?? gBrowser.addTrustedTab(Services.io.newURI(url).spec, newTabOptions);
|
| 77 | + |
| 78 | + // Example: copy any context ID |
77 | 79 | if (currentTab.hasAttribute('zenDefaultUserContextId')) {
|
78 | 80 | newTab.setAttribute('zenDefaultUserContextId', true);
|
79 | 81 | }
|
| 82 | + |
| 83 | + // Insert the new tab as a child of the existing tab's content |
80 | 84 | currentTab.querySelector('.tab-content').appendChild(newTab);
|
81 | 85 | newTab.setAttribute('zen-glance-tab', true);
|
82 | 86 | newTab.setAttribute('glance-id', newUUID);
|
83 | 87 | currentTab.setAttribute('glance-id', newUUID);
|
| 88 | + |
84 | 89 | this.#glances.set(newUUID, {
|
85 | 90 | tab: newTab,
|
86 | 91 | parentTab: currentTab,
|
|
92 | 97 | }
|
93 | 98 |
|
94 | 99 | fillOverlay(browser) {
|
| 100 | + // Save references to the parent containers |
95 | 101 | this.overlay = browser.closest('.browserSidebarContainer');
|
96 | 102 | this.browserWrapper = browser.closest('.browserContainer');
|
97 | 103 | this.contentWrapper = browser.closest('.browserStack');
|
98 | 104 | }
|
99 | 105 |
|
100 | 106 | showSidebarButtons(animate = false) {
|
| 107 | + // Animate in if hidden |
101 | 108 | if (this.sidebarButtons.hasAttribute('hidden') && animate) {
|
102 | 109 | gZenUIManager.motion.animate(
|
103 | 110 | this.sidebarButtons.querySelectorAll('toolbarbutton'),
|
|
113 | 120 | }
|
114 | 121 |
|
115 | 122 | openGlance(data, existingTab = null, ownerTab = null) {
|
| 123 | + // If a glance is already open, do nothing |
116 | 124 | if (this.#currentBrowser) {
|
117 | 125 | return;
|
118 | 126 | }
|
| 127 | + |
| 128 | + // If the current parent tab is selected, switch to the glance tab |
119 | 129 | if (gBrowser.selectedTab === this.#currentParentTab) {
|
120 | 130 | gBrowser.selectedTab = this.#currentTab;
|
121 | 131 | return;
|
122 | 132 | }
|
| 133 | + |
123 | 134 | this.animatingOpen = true;
|
124 | 135 | this._animating = true;
|
125 | 136 |
|
| 137 | + // Gather initial positions |
126 | 138 | const initialX = data.x;
|
127 | 139 | const initialY = data.y;
|
128 | 140 | const initialWidth = data.width;
|
129 | 141 | const initialHeight = data.height;
|
130 | 142 |
|
| 143 | + // Clean up any leftover states |
131 | 144 | this.browserWrapper?.removeAttribute('animate');
|
132 | 145 | this.browserWrapper?.removeAttribute('animate-end');
|
133 | 146 | this.browserWrapper?.removeAttribute('animate-full');
|
134 | 147 | this.browserWrapper?.removeAttribute('has-finished-animation');
|
135 | 148 | this.overlay?.removeAttribute('post-fade-out');
|
136 | 149 |
|
| 150 | + // Create the new tab |
137 | 151 | const currentTab = ownerTab ?? gBrowser.selectedTab;
|
138 |
| - |
139 | 152 | const browserElement = this.createBrowserElement(data.url, currentTab, existingTab);
|
140 | 153 |
|
| 154 | + // Fill references |
141 | 155 | this.fillOverlay(browserElement);
|
142 | 156 |
|
| 157 | + const container = document.getElementById('glance-wrapper'); |
| 158 | + if (container) { |
| 159 | + container.appendChild(this.sidebarButtons); |
| 160 | + } |
| 161 | + |
| 162 | + // Start overlay |
143 | 163 | this.overlay.classList.add('zen-glance-overlay');
|
144 | 164 |
|
| 165 | + // Animate open |
145 | 166 | this.browserWrapper.removeAttribute('animate-end');
|
146 | 167 | window.requestAnimationFrame(() => {
|
| 168 | + // "Quick open" logic |
147 | 169 | this.quickOpenGlance({ dontOpenButtons: true });
|
| 170 | + // Show the sidebar buttons |
148 | 171 | this.showSidebarButtons(true);
|
149 | 172 |
|
| 173 | + // Animate the parent container |
150 | 174 | gZenUIManager.motion.animate(
|
151 | 175 | this.#currentParentTab.linkedBrowser.closest('.browserSidebarContainer'),
|
152 | 176 | {
|
|
160 | 184 | bounce: 0.2,
|
161 | 185 | }
|
162 | 186 | );
|
| 187 | + |
| 188 | + // Start the transition |
163 | 189 | this.#currentBrowser.setAttribute('animate-glance-open', true);
|
164 | 190 | this.overlay.removeAttribute('fade-out');
|
165 | 191 | this.browserWrapper.setAttribute('animate', true);
|
| 192 | + |
166 | 193 | const top = initialY + initialHeight / 2;
|
167 | 194 | const left = initialX + initialWidth / 2;
|
168 | 195 | this.browserWrapper.style.top = `${top}px`;
|
169 | 196 | this.browserWrapper.style.left = `${left}px`;
|
170 | 197 | this.browserWrapper.style.width = `${initialWidth}px`;
|
171 | 198 | this.browserWrapper.style.height = `${initialHeight}px`;
|
172 | 199 | this.browserWrapper.style.opacity = 0.8;
|
| 200 | + |
| 201 | + // Save original position for closing animation |
173 | 202 | this.#glances.get(this.#currentGlanceID).originalPosition = {
|
174 | 203 | top: this.browserWrapper.style.top,
|
175 | 204 | left: this.browserWrapper.style.left,
|
176 | 205 | width: this.browserWrapper.style.width,
|
177 | 206 | height: this.browserWrapper.style.height,
|
178 | 207 | };
|
| 208 | + |
179 | 209 | this.browserWrapper.style.transform = 'translate(-50%, -50%)';
|
180 | 210 | this.overlay.style.overflow = 'visible';
|
| 211 | + |
| 212 | + // Animate up to final size |
181 | 213 | gZenUIManager.motion
|
182 | 214 | .animate(
|
183 | 215 | this.browserWrapper,
|
|
236 | 268 | this.closingGlance = true;
|
237 | 269 | this._animating = true;
|
238 | 270 |
|
| 271 | + // Insert tab at correct index |
239 | 272 | gBrowser._insertTabAtIndex(this.#currentTab, {
|
240 | 273 | index: this.getTabPosition(this.#currentParentTab),
|
241 | 274 | });
|
242 | 275 |
|
243 | 276 | let quikcCloseZen = false;
|
244 | 277 | if (onTabClose) {
|
245 |
| - // Create new tab if no more ex |
| 278 | + // If there's only one tab left, open a new one |
246 | 279 | if (gBrowser.tabs.length === 1) {
|
247 | 280 | BrowserCommands.openTab();
|
248 | 281 | return;
|
249 | 282 | }
|
250 | 283 | }
|
251 | 284 |
|
252 |
| - // do NOT touch here, I don't know what it does, but it works... |
| 285 | + // do NOT touch here, unknown but functional |
253 | 286 | this.#currentTab.style.display = 'none';
|
254 | 287 | this.overlay.setAttribute('fade-out', true);
|
255 | 288 | this.overlay.style.pointerEvents = 'none';
|
256 | 289 | this.quickCloseGlance({ justAnimateParent: true, clearID: false });
|
| 290 | + |
| 291 | + // Animate the parent container |
257 | 292 | const originalPosition = this.#glances.get(this.#currentGlanceID).originalPosition;
|
258 | 293 | gZenUIManager.motion
|
259 | 294 | .animate(
|
|
289 | 324 | return;
|
290 | 325 | }
|
291 | 326 |
|
| 327 | + // Final close |
292 | 328 | if (!onTabClose || quikcCloseZen) {
|
293 | 329 | this.quickCloseGlance({ clearID: false });
|
294 | 330 | }
|
| 331 | + |
295 | 332 | this.overlay.removeAttribute('fade-out');
|
296 | 333 | this.browserWrapper.removeAttribute('animate');
|
297 | 334 |
|
|
330 | 367 | this._animating = false;
|
331 | 368 | this.closingGlance = false;
|
332 | 369 |
|
| 370 | + // If we had another Glance queued, open it |
333 | 371 | if (this.#currentGlanceID) {
|
334 | 372 | this.quickOpenGlance();
|
335 | 373 | }
|
|
349 | 387 | parentBrowserContainer.classList.add('zen-glance-background');
|
350 | 388 | parentBrowserContainer.classList.remove('zen-glance-overlay');
|
351 | 389 | parentBrowserContainer.classList.add('deck-selected');
|
| 390 | + |
352 | 391 | this.#currentParentTab.linkedBrowser.zenModeActive = true;
|
353 | 392 | this.#currentParentTab.linkedBrowser.docShellIsActive = true;
|
354 | 393 | this.#currentBrowser.zenModeActive = true;
|
|
454 | 493 | return false;
|
455 | 494 | }
|
456 | 495 | this.closeGlance({ onTabClose: true, setNewID: isDifferent ? oldGlanceID : null, isDifferent });
|
457 |
| - // only keep continueing tab close if we are not on the currently selected tab |
458 | 496 | return !isDifferent;
|
459 | 497 | }
|
460 | 498 | return false;
|
461 | 499 | }
|
462 | 500 |
|
463 | 501 | tabDomainsDiffer(tab1, url2) {
|
464 | 502 | try {
|
465 |
| - if (!tab1) { |
466 |
| - return true; |
467 |
| - } |
| 503 | + if (!tab1) return true; |
468 | 504 | let url1 = tab1.linkedBrowser.currentURI.spec;
|
469 |
| - if (url1.startsWith('about:')) { |
470 |
| - return true; |
471 |
| - } |
| 505 | + if (url1.startsWith('about:')) return true; |
472 | 506 | return Services.io.newURI(url1).host !== url2.host;
|
473 | 507 | } catch (e) {
|
474 | 508 | return true;
|
|
489 | 523 |
|
490 | 524 | onTabOpen(browser, uri) {
|
491 | 525 | let tab = gBrowser.getTabForBrowser(browser);
|
492 |
| - if (!tab) { |
493 |
| - return; |
494 |
| - } |
| 526 | + if (!tab) return; |
495 | 527 | try {
|
496 | 528 | if (this.shouldOpenTabInGlance(tab, uri)) {
|
497 | 529 | const browserRect = gBrowser.tabbox.getBoundingClientRect();
|
|
529 | 561 | this.#currentTab.removeAttribute('glance-id');
|
530 | 562 | this.#currentParentTab.removeAttribute('glance-id');
|
531 | 563 | gBrowser.selectedTab = this.#currentTab;
|
| 564 | + |
532 | 565 | this.#currentParentTab.linkedBrowser.closest('.browserSidebarContainer').classList.remove('zen-glance-background');
|
533 | 566 | this.#currentParentTab._visuallySelected = false;
|
534 | 567 | this.hideSidebarButtons();
|
| 568 | + |
535 | 569 | if (gReduceMotion) {
|
536 | 570 | this.finishOpeningGlance();
|
537 | 571 | return;
|
|
552 | 586 |
|
553 | 587 | openGlanceForBookmark(event) {
|
554 | 588 | const activationMethod = Services.prefs.getStringPref('zen.glance.activation-method', 'ctrl');
|
555 |
| - |
556 |
| - if (activationMethod === 'ctrl' && !event.ctrlKey) { |
557 |
| - return; |
558 |
| - } else if (activationMethod === 'alt' && !event.altKey) { |
559 |
| - return; |
560 |
| - } else if (activationMethod === 'shift' && !event.shiftKey) { |
561 |
| - return; |
562 |
| - } else if (activationMethod === 'meta' && !event.metaKey) { |
563 |
| - return; |
564 |
| - } else if (activationMethod === 'mantain' || typeof activationMethod === 'undefined') { |
565 |
| - return; |
566 |
| - } |
| 589 | + if (activationMethod === 'ctrl' && !event.ctrlKey) return; |
| 590 | + if (activationMethod === 'alt' && !event.altKey) return; |
| 591 | + if (activationMethod === 'shift' && !event.shiftKey) return; |
| 592 | + if (activationMethod === 'meta' && !event.metaKey) return; |
| 593 | + if (activationMethod === 'mantain' || typeof activationMethod === 'undefined') return; |
567 | 594 |
|
568 | 595 | event.preventDefault();
|
569 | 596 | event.stopPropagation();
|
|
576 | 603 | width: rect.width,
|
577 | 604 | height: rect.height,
|
578 | 605 | };
|
579 |
| - |
580 | 606 | this.openGlance(data);
|
581 |
| - |
582 | 607 | return false;
|
583 | 608 | }
|
584 | 609 |
|
|
587 | 612 | }
|
588 | 613 | }
|
589 | 614 |
|
| 615 | + // Expose globally |
590 | 616 | window.gZenGlanceManager = new ZenGlanceManager();
|
591 | 617 |
|
| 618 | + // Register window actors if needed |
592 | 619 | function registerWindowActors() {
|
593 | 620 | if (Services.prefs.getBoolPref('zen.glance.enabled', true)) {
|
594 | 621 | gZenActorsManager.addJSWindowActor('ZenGlance', {
|
|
609 | 636 | });
|
610 | 637 | }
|
611 | 638 | }
|
612 |
| - |
613 | 639 | registerWindowActors();
|
614 | 640 | }
|
0 commit comments