Skip to content

Commit

Permalink
Fix pinned tab state and favicon handling
Browse files Browse the repository at this point in the history
Fixes several issues related to pinned tab restoration and
favicon display:

- Ensures pinned tabs are not loaded in private windows.
- Improves favicon caching and setting for pinned tabs.
- Fixes an issue where the tab state was not properly
  restored, leading to incorrect icons and titles.
- Uses base64 encoded favicon data to prevent issues with
  favicon loading and updates the favicon loading logic to
  be more robust.
- Adds a call to `gBrowser._updateTabBarForPinnedTabs` to
  ensure the tab bar is updated correctly after restoring
  pinned tabs, which fixes a UI issue where the tab bar
  wouldn't refresh properly until interacted with.
- Improves tab initialization by explicitly calling
  `newTab.initialize()` after restoring a pinned tab. This
  fixes an issue with tabs not rendering
  icon and label after being restored.

The changes improve the reliability of pinned tab
restoration and ensure that favicons are displayed
correctly.
  • Loading branch information
kristijanribaric committed Nov 10, 2024
1 parent 259b25a commit ab2df8c
Showing 1 changed file with 76 additions and 35 deletions.
111 changes: 76 additions & 35 deletions src/browser/base/zen-components/ZenPinnedTabManager.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
class ZenPinnedTabManager extends ZenPreloadedFeature {

init() {
if (!this.enabled) {
if (!this.enabled || PrivateBrowsingUtils.isWindowPrivate(window)) {
return;
}
this.observer = new ZenPinnedTabsObserver();
Expand All @@ -51,7 +51,7 @@
}

async initTabs() {
if (!this.enabled) {
if (!this.enabled || PrivateBrowsingUtils.isWindowPrivate(window)) {
return;
}
await ZenPinnedTabsStorage.init();
Expand Down Expand Up @@ -81,10 +81,10 @@
// Enhance pins with favicons
const enhancedPins = await Promise.all(pins.map(async pin => {
try {
const faviconData = await PlacesUtils.promiseFaviconData(pin.url);
const image = await this.getFaviconAsBase64(pin.url);
return {
...pin,
iconUrl: faviconData?.uri?.spec || null
iconUrl: image || null
};
} catch(ex) {
// If favicon fetch fails, continue without icon
Expand Down Expand Up @@ -142,23 +142,30 @@
continue; // Skip pins that already have tabs
}

let newTab = gBrowser.addTrustedTab(pin.url, {
let params = {
skipAnimation: true,
userContextId: pin.containerTabId || 0,
allowInheritPrincipal: false,
skipBackgroundNotify: true,
userContextId: pin.containerTabId || 0,
createLazyBrowser: true,
skipLoad: true,
});
noInitialLabel: false
};

// Create and initialize the tab
let newTab = gBrowser.addTrustedTab(pin.url, params);

// Set the favicon from cache
if (!!pin.iconUrl) {
// TODO: Figure out if there is a better way -
// calling gBrowser.setIcon messes shit up and should be avoided. I think this works for now.
newTab.setAttribute("image", pin.iconUrl);
// Set initial label/title
if (pin.title) {
gBrowser.setInitialTabTitle(newTab, pin.title);
}

// Set the icon if we have it cached
if (pin.iconUrl) {
gBrowser.setIcon(newTab, pin.iconUrl);
}

newTab.setAttribute("zen-pin-id", pin.uuid);
gBrowser.setInitialTabTitle(newTab, pin.title);

if (pin.workspaceUuid) {
newTab.setAttribute("zen-workspace-id", pin.workspaceUuid);
Expand All @@ -168,13 +175,32 @@
newTab.setAttribute("zen-essential", "true");
}

// Initialize browser state if needed
if (!newTab.linkedBrowser._remoteAutoRemoved) {
let state = {
entries: [{
url: pin.url,
title: pin.title,
triggeringPrincipal_base64: E10SUtils.SERIALIZED_SYSTEMPRINCIPAL
}],
userContextId: pin.containerTabId || 0,
image: pin.iconUrl
};

SessionStore.setTabState(newTab, state);
}

gBrowser.pinTab(newTab);

newTab.initialize();
}

// Restore active tab
if (!activeTab.closing) {
gBrowser.selectedTab = activeTab;
}

gBrowser._updateTabBarForPinnedTabs();
}

_onPinnedTabEvent(action, event) {
Expand Down Expand Up @@ -370,36 +396,51 @@

async _resetTabToStoredState(tab) {
const id = tab.getAttribute("zen-pin-id");

if (!id) {
return;
}

const pin = this._pinsCache.find(pin => pin.uuid === id);
if (!pin) {
return;
}

if (pin) {
const tabState = SessionStore.getTabState(tab);
const state = JSON.parse(tabState);
let icon = undefined;
try {
icon = await PlacesUtils.promiseFaviconData(pin.url);
} catch (e) {
console.warn("Error trying to get favicon for pinned tab", e);
}
const tabState = SessionStore.getTabState(tab);
const state = JSON.parse(tabState);

state.entries = [{
url: pin.url,
title: pin.title,
triggeringPrincipal_base64: lazy.E10SUtils.SERIALIZED_SYSTEMPRINCIPAL
}];
if (icon instanceof Ci.nsIURI || typeof icon === "string") {
state.image = icon;
} else {
state.image = null;
}
state.index = 0;
state.entries = [{
url: pin.url,
title: pin.title,
triggeringPrincipal_base64: lazy.E10SUtils.SERIALIZED_SYSTEMPRINCIPAL
}];

state.image = pin.iconUrl || null;
state.index = 0;

SessionStore.setTabState(tab, state);
SessionStore.setTabState(tab, state);
}

async getFaviconAsBase64(pageUrl) {
try {
// Get the favicon data
const faviconData = await PlacesUtils.promiseFaviconData(pageUrl);

// The data comes as an array buffer, we need to convert it to base64
// First create a byte array from the data
const array = new Uint8Array(faviconData.data);

// Convert to base64
const base64String = btoa(
Array.from(array)
.map(b => String.fromCharCode(b))
.join('')
);

// Return as a proper data URL
return `data:${faviconData.mimeType};base64,${base64String}`;
} catch (ex) {
console.error("Failed to get favicon:", ex);
return null;
}
}

Expand Down

0 comments on commit ab2df8c

Please sign in to comment.