Synced workspaces across windows and also implemented MultiWindowFeatures

This commit is contained in:
mauro-balades 2024-10-01 23:21:38 +02:00
parent fbe5727705
commit 2ea7024eea
No known key found for this signature in database
GPG key ID: CBD57A2AEDBDA1FB
5 changed files with 273 additions and 244 deletions

42
src/ZenCommonUtils.mjs Normal file
View file

@ -0,0 +1,42 @@
var gZenOperatingSystemCommonUtils = {
kZenOSToSmallName: {
WINNT: 'windows',
Darwin: 'macos',
Linux: 'linux',
},
get currentOperatingSystem() {
let os = Services.appinfo.OS;
return this.kZenOSToSmallName[os];
},
};
class ZenMultiWindowFeature {
constructor() {}
static get browsers() {
return Services.wm.getEnumerator('navigator:browser');
}
static get currentBrowser() {
return Services.wm.getMostRecentWindow('navigator:browser');
}
isActiveWindow() {
return ZenMultiWindowFeature.currentBrowser === window;
}
async foreachWindowAsActive(callback) {
if (!this.isActiveWindow()) {
return;
}
for (const browser of ZenMultiWindowFeature.browsers) {
try {
await callback(browser);
} catch (e) {
console.error(e);
}
}
}
}

View file

@ -788,7 +788,7 @@ var gZenKeyboardShortcutsManager = {
}, },
_applyShortcuts() { _applyShortcuts() {
for (const browser of ZenThemesCommon.browsers) { for (const browser of ZenMultiWindowFeature.browsers) {
let mainKeyset = browser.document.getElementById('mainKeyset'); let mainKeyset = browser.document.getElementById('mainKeyset');
if (!mainKeyset) { if (!mainKeyset) {
throw new Error('Main keyset not found'); throw new Error('Main keyset not found');

View file

@ -1,17 +1,6 @@
var ZenThemesCommon = { var ZenThemesCommon = {
kZenOSToSmallName: {
WINNT: 'windows',
Darwin: 'macos',
Linux: 'linux',
},
kZenColors: ['#aac7ff', '#74d7cb', '#a0d490', '#dec663', '#ffb787', '#dec1b1', '#ffb1c0', '#ddbfc3', '#f6b0ea', '#d4bbff'], kZenColors: ['#aac7ff', '#74d7cb', '#a0d490', '#dec663', '#ffb787', '#dec1b1', '#ffb1c0', '#ddbfc3', '#f6b0ea', '#d4bbff'],
get currentOperatingSystem() {
let os = Services.appinfo.OS;
return this.kZenOSToSmallName[os];
},
get browsers() { get browsers() {
return Services.wm.getEnumerator('navigator:browser'); return Services.wm.getEnumerator('navigator:browser');
}, },
@ -63,8 +52,8 @@ var ZenThemesCommon = {
const isNegation = negation === '!'; const isNegation = negation === '!';
if ( if (
(isNegation && os === this.currentOperatingSystem) || (isNegation && os === gZenOperatingSystemCommonUtils.currentOperatingSystem) ||
(os !== '' && os !== this.currentOperatingSystem && !isNegation) (os !== '' && os !== gZenOperatingSystemCommonUtils.currentOperatingSystem && !isNegation)
) { ) {
continue; continue;
} }
@ -80,7 +69,7 @@ var ZenThemesCommon = {
return newThemePreferences; return newThemePreferences;
} }
return preferences.filter(({ disabledOn = [] }) => !disabledOn.includes(this.currentOperatingSystem)); return preferences.filter(({ disabledOn = [] }) => !disabledOn.includes(gZenOperatingSystemCommonUtils.currentOperatingSystem));
}, },
throttle(mainFunction, delay) { throttle(mainFunction, delay) {

View file

@ -177,7 +177,7 @@ var gZenThemeImporter = new (class {
} }
writeToDom(themesWithPreferences) { writeToDom(themesWithPreferences) {
for (const browser of ZenThemesCommon.browsers) { for (const browser of ZenMultiWindowFeature.browsers) {
for (const { enabled, preferences, name } of themesWithPreferences) { for (const { enabled, preferences, name } of themesWithPreferences) {
const sanitizedName = `theme-${name?.replaceAll(/\s/g, '-')?.replaceAll(/[^A-z_-]+/g, '')}`; const sanitizedName = `theme-${name?.replaceAll(/\s/g, '-')?.replaceAll(/[^A-z_-]+/g, '')}`;

View file

@ -1,9 +1,9 @@
var ZenWorkspaces = { var ZenWorkspaces = new class extends ZenMultiWindowFeature {
/** /**
* Stores workspace IDs and their last selected tabs. * Stores workspace IDs and their last selected tabs.
*/ */
_lastSelectedWorkspaceTabs: {}, _lastSelectedWorkspaceTabs = {}
async init() { async init() {
if (!this.shouldHaveWorkspaces) { if (!this.shouldHaveWorkspaces) {
@ -11,30 +11,36 @@ var ZenWorkspaces = {
return; // We are in a hidden window, don't initialize ZenWorkspaces return; // We are in a hidden window, don't initialize ZenWorkspaces
} }
console.info('ZenWorkspaces: Initializing ZenWorkspaces...'); console.info('ZenWorkspaces: Initializing ZenWorkspaces...');
XPCOMUtils.defineLazyPreferenceGetter(
this,
"shouldShowIconStrip",
"zen.workspaces.show-icon-strip",
true,
this._expandWorkspacesStrip.bind(this)
);
ChromeUtils.defineLazyGetter(this, 'tabContainer', () => document.getElementById('tabbrowser-tabs'));
await this.initializeWorkspaces(); await this.initializeWorkspaces();
console.info('ZenWorkspaces: ZenWorkspaces initialized'); console.info('ZenWorkspaces: ZenWorkspaces initialized');
}, }
get shouldShowIconStrip() {
delete this.shouldShowIconStrip;
this.shouldShowIconStrip = Services.prefs.getBoolPref('zen.workspaces.show-icon-strip', true);
return this.shouldShowIconStrip;
},
get shouldHaveWorkspaces() { get shouldHaveWorkspaces() {
delete this.shouldHaveWorkspaces; if (typeof this._shouldHaveWorkspaces === 'undefined') {
let docElement = document.documentElement; let docElement = document.documentElement;
this.shouldHaveWorkspaces = !(docElement.hasAttribute('privatebrowsingmode') this._shouldHaveWorkspaces = !(docElement.hasAttribute('privatebrowsingmode')
|| docElement.getAttribute('chromehidden').includes('toolbar') || docElement.getAttribute('chromehidden').includes('toolbar')
|| docElement.getAttribute('chromehidden').includes('menubar')); || docElement.getAttribute('chromehidden').includes('menubar'));
return this.shouldHaveWorkspaces; return this._shouldHaveWorkspaces;
}, }
return this._shouldHaveWorkspaces;
}
get workspaceEnabled() { get workspaceEnabled() {
delete this.workspaceEnabled; if (typeof this._workspaceEnabled === 'undefined') {
this.workspaceEnabled = Services.prefs.getBoolPref('zen.workspaces.enabled', false) && this.shouldHaveWorkspaces; this._workspaceEnabled = Services.prefs.getBoolPref('zen.workspaces.enabled', false) && this.shouldHaveWorkspaces;
return this.workspaceEnabled; return this._workspaceEnabled;
}, }
return this._workspaceEnabled;
}
getActiveWorkspaceFromCache() { getActiveWorkspaceFromCache() {
try { try {
@ -42,12 +48,12 @@ var ZenWorkspaces = {
} catch (e) { } catch (e) {
return null; return null;
} }
}, }
// Wrorkspaces saving/loading // Wrorkspaces saving/loading
get _storeFile() { get _storeFile() {
return PathUtils.join(PathUtils.profileDir, 'zen-workspaces', 'Workspaces.json'); return PathUtils.join(PathUtils.profileDir, 'zen-workspaces', 'Workspaces.json');
}, }
async _workspaces() { async _workspaces() {
if (!this._workspaceCache) { if (!this._workspaceCache) {
@ -57,7 +63,7 @@ var ZenWorkspaces = {
} }
} }
return this._workspaceCache; return this._workspaceCache;
}, }
async onWorkspacesEnabledChanged() { async onWorkspacesEnabledChanged() {
if (this.workspaceEnabled) { if (this.workspaceEnabled) {
@ -69,16 +75,10 @@ var ZenWorkspaces = {
gBrowser.showTab(tab); gBrowser.showTab(tab);
} }
} }
}, }
async onWorkspacesIconStripChanged() {
this.shouldShowIconStrip = Services.prefs.getBoolPref('zen.workspaces.show-icon-strip', true);
await this._expandWorkspacesStrip();
},
async initializeWorkspaces() { async initializeWorkspaces() {
Services.prefs.addObserver('zen.workspaces.enabled', this.onWorkspacesEnabledChanged.bind(this)); Services.prefs.addObserver('zen.workspaces.enabled', this.onWorkspacesEnabledChanged.bind(this));
Services.prefs.addObserver('zen.workspaces.show-icon-strip', this.onWorkspacesIconStripChanged.bind(this));
let file = new FileUtils.File(this._storeFile); let file = new FileUtils.File(this._storeFile);
if (!file.exists()) { if (!file.exists()) {
await IOUtils.writeJSON(this._storeFile, {}); await IOUtils.writeJSON(this._storeFile, {});
@ -108,7 +108,7 @@ var ZenWorkspaces = {
this.changeWorkspace(activeWorkspace, true); this.changeWorkspace(activeWorkspace, true);
} }
} }
}, }
handleTabClose(event) { handleTabClose(event) {
if (this.__contextIsDelete) { if (this.__contextIsDelete) {
@ -125,11 +125,11 @@ var ZenWorkspaces = {
this.changeWorkspace({ uuid: workspaceID }, true); this.changeWorkspace({ uuid: workspaceID }, true);
} }
} }
}, }
_kIcons: JSON.parse(Services.prefs.getStringPref("zen.workspaces.icons")).map((icon) => ( _kIcons = JSON.parse(Services.prefs.getStringPref("zen.workspaces.icons")).map((icon) => (
(typeof Intl.Segmenter !== 'undefined') ? new Intl.Segmenter().segment(icon).containing().segment : Array.from(icon)[0] (typeof Intl.Segmenter !== 'undefined') ? new Intl.Segmenter().segment(icon).containing().segment : Array.from(icon)[0]
)), ))
_initializeWorkspaceCreationIcons() { _initializeWorkspaceCreationIcons() {
let container = document.getElementById('PanelUI-zen-workspaces-create-icons-container'); let container = document.getElementById('PanelUI-zen-workspaces-create-icons-container');
@ -148,7 +148,7 @@ var ZenWorkspaces = {
}).bind(this, button); }).bind(this, button);
container.appendChild(button); container.appendChild(button);
} }
}, }
_initializeWorkspaceEditIcons() { _initializeWorkspaceEditIcons() {
let container = this._workspaceEditIconsContainer; let container = this._workspaceEditIconsContainer;
@ -168,7 +168,7 @@ var ZenWorkspaces = {
}).bind(this, button); }).bind(this, button);
container.appendChild(button); container.appendChild(button);
} }
}, }
async saveWorkspace(workspaceData) { async saveWorkspace(workspaceData) {
let json = await IOUtils.readJSON(this._storeFile); let json = await IOUtils.readJSON(this._storeFile);
@ -186,7 +186,7 @@ var ZenWorkspaces = {
this._workspaceCache = null; this._workspaceCache = null;
await this._updateWorkspacesChangeContextMenu(); await this._updateWorkspacesChangeContextMenu();
}, }
async removeWorkspace(windowID) { async removeWorkspace(windowID) {
let json = await this._workspaces(); let json = await this._workspaces();
@ -198,24 +198,24 @@ var ZenWorkspaces = {
await this.unsafeSaveWorkspaces(json); await this.unsafeSaveWorkspaces(json);
await this._propagateWorkspaceData(); await this._propagateWorkspaceData();
await this._updateWorkspacesChangeContextMenu(); await this._updateWorkspacesChangeContextMenu();
}, }
async saveWorkspaces() { async saveWorkspaces() {
await IOUtils.writeJSON(this._storeFile, await this._workspaces()); await IOUtils.writeJSON(this._storeFile, await this._workspaces());
this._workspaceCache = null; this._workspaceCache = null;
}, }
async unsafeSaveWorkspaces(workspaces) { async unsafeSaveWorkspaces(workspaces) {
await IOUtils.writeJSON(this._storeFile, workspaces); await IOUtils.writeJSON(this._storeFile, workspaces);
this._workspaceCache = workspaces; this._workspaceCache = workspaces;
}, }
// Workspaces dialog UI management // Workspaces dialog UI management
openSaveDialog() { openSaveDialog() {
let parentPanel = document.getElementById('PanelUI-zen-workspaces-multiview'); let parentPanel = document.getElementById('PanelUI-zen-workspaces-multiview');
PanelUI.showSubView('PanelUI-zen-workspaces-create', parentPanel); PanelUI.showSubView('PanelUI-zen-workspaces-create', parentPanel);
}, }
async openEditDialog(workspaceUuid) { async openEditDialog(workspaceUuid) {
this._workspaceEditDialog.setAttribute('data-workspace-uuid', workspaceUuid); this._workspaceEditDialog.setAttribute('data-workspace-uuid', workspaceUuid);
@ -235,16 +235,16 @@ var ZenWorkspaces = {
}); });
let parentPanel = document.getElementById('PanelUI-zen-workspaces-multiview'); let parentPanel = document.getElementById('PanelUI-zen-workspaces-multiview');
PanelUI.showSubView('PanelUI-zen-workspaces-edit', parentPanel); PanelUI.showSubView('PanelUI-zen-workspaces-edit', parentPanel);
}, }
closeWorkspacesSubView() { closeWorkspacesSubView() {
let parentPanel = document.getElementById('PanelUI-zen-workspaces-multiview'); let parentPanel = document.getElementById('PanelUI-zen-workspaces-multiview');
parentPanel.goBack(); parentPanel.goBack();
}, }
workspaceHasIcon(workspace) { workspaceHasIcon(workspace) {
return typeof workspace.icon !== 'undefined' && workspace.icon !== ''; return typeof workspace.icon !== 'undefined' && workspace.icon !== '';
}, }
getWorkspaceIcon(workspace) { getWorkspaceIcon(workspace) {
if (this.workspaceHasIcon(workspace)) { if (this.workspaceHasIcon(workspace)) {
@ -254,109 +254,112 @@ var ZenWorkspaces = {
return new Intl.Segmenter().segment(workspace.name).containing().segment.toUpperCase(); return new Intl.Segmenter().segment(workspace.name).containing().segment.toUpperCase();
} }
return Array.from(workspace.name)[0].toUpperCase(); return Array.from(workspace.name)[0].toUpperCase();
}, }
get shouldShowContainers() { get shouldShowContainers() {
return Services.prefs.getBoolPref('privacy.userContext.ui.enabled') && return Services.prefs.getBoolPref('privacy.userContext.ui.enabled') &&
ContextualIdentityService.getPublicIdentities().length > 0; ContextualIdentityService.getPublicIdentities().length > 0;
}, }
async _propagateWorkspaceData({ async _propagateWorkspaceData({
ignoreStrip = false ignoreStrip = false
} = {}) { } = {}) {
let currentContainer = document.getElementById('PanelUI-zen-workspaces-current-info'); await this.foreachWindowAsActive(async (browser) => {
let workspaceList = document.getElementById('PanelUI-zen-workspaces-list'); let currentContainer = browser.document.getElementById('PanelUI-zen-workspaces-current-info');
if (!ignoreStrip) { let workspaceList = browser.document.getElementById('PanelUI-zen-workspaces-list');
await this._expandWorkspacesStrip(); const createWorkspaceElement = (workspace) => {
} let element = browser.document.createXULElement('toolbarbutton');
const createWorkspaceElement = (workspace) => { element.className = 'subviewbutton';
let element = document.createXULElement('toolbarbutton'); element.setAttribute('tooltiptext', workspace.name);
element.className = 'subviewbutton'; element.setAttribute('zen-workspace-id', workspace.uuid);
element.setAttribute('tooltiptext', workspace.name); if (workspace.used) {
element.setAttribute('zen-workspace-id', workspace.uuid); element.setAttribute('active', 'true');
if (workspace.used) {
element.setAttribute('active', 'true');
}
if (workspace.default) {
element.setAttribute('default', 'true');
}
const containerGroup = ContextualIdentityService.getPublicIdentities().find(
(container) => container.userContextId === workspace.containerTabId
);
if (containerGroup) {
element.classList.add('identity-color-' + containerGroup.color);
element.setAttribute('data-usercontextid', containerGroup.userContextId);
}
let childs = window.MozXULElement.parseXULToFragment(`
<div class="zen-workspace-icon">
</div>
<vbox>
<div class="zen-workspace-name">
</div>
<div class="zen-workspace-container" ${containerGroup ? '' : 'hidden="true"'}>
</div>
</vbox>
<toolbarbutton closemenu="none" class="toolbarbutton-1 zen-workspace-actions">
<image class="toolbarbutton-icon" id="zen-workspace-actions-menu-icon"></image>
</toolbarbutton>
`);
// use text content instead of innerHTML to avoid XSS
childs.querySelector('.zen-workspace-icon').textContent = this.getWorkspaceIcon(workspace);
childs.querySelector('.zen-workspace-name').textContent = workspace.name;
if (containerGroup) {
childs.querySelector('.zen-workspace-container').textContent = ContextualIdentityService.getUserContextLabel(
containerGroup.userContextId
);
}
childs.querySelector('.zen-workspace-actions').addEventListener('command', (event) => {
let button = event.target;
this._contextMenuId = button.closest('toolbarbutton[zen-workspace-id]').getAttribute('zen-workspace-id');
const popup = button.ownerDocument.getElementById('zenWorkspaceActionsMenu');
popup.openPopup(button, 'after_end');
});
element.appendChild(childs);
element.onclick = (async () => {
if (event.target.closest('.zen-workspace-actions')) {
return; // Ignore clicks on the actions button
} }
await this.changeWorkspace(workspace); if (workspace.default) {
let panel = document.getElementById('PanelUI-zen-workspaces'); element.setAttribute('default', 'true');
PanelMultiView.hidePopup(panel); }
document.getElementById('zen-workspaces-button').removeAttribute('open'); const containerGroup = browser.ContextualIdentityService.getPublicIdentities().find(
}).bind(this, workspace); (container) => container.userContextId === workspace.containerTabId
return element; );
}; if (containerGroup) {
let workspaces = await this._workspaces(); element.classList.add('identity-color-' + containerGroup.color);
let activeWorkspace = workspaces.workspaces.find((workspace) => workspace.used); element.setAttribute('data-usercontextid', containerGroup.userContextId);
currentContainer.innerHTML = ''; }
workspaceList.innerHTML = ''; let childs = browser.MozXULElement.parseXULToFragment(`
workspaceList.parentNode.style.display = 'flex'; <div class="zen-workspace-icon">
if (workspaces.workspaces.length - 1 <= 0) { </div>
workspaceList.innerHTML = 'No workspaces available'; <vbox>
workspaceList.setAttribute('empty', 'true'); <div class="zen-workspace-name">
} else { </div>
workspaceList.removeAttribute('empty'); <div class="zen-workspace-container" ${containerGroup ? '' : 'hidden="true"'}>
} </div>
if (activeWorkspace) { </vbox>
let currentWorkspace = createWorkspaceElement(activeWorkspace); <toolbarbutton closemenu="none" class="toolbarbutton-1 zen-workspace-actions">
currentContainer.appendChild(currentWorkspace); <image class="toolbarbutton-icon" id="zen-workspace-actions-menu-icon"></image>
} </toolbarbutton>
for (let workspace of workspaces.workspaces) { `);
if (workspace.used) {
continue; // use text content instead of innerHTML to avoid XSS
childs.querySelector('.zen-workspace-icon').textContent = browser.ZenWorkspaces.getWorkspaceIcon(workspace);
childs.querySelector('.zen-workspace-name').textContent = workspace.name;
if (containerGroup) {
childs.querySelector('.zen-workspace-container').textContent = ContextualIdentityService.getUserContextLabel(
containerGroup.userContextId
);
}
childs.querySelector('.zen-workspace-actions').addEventListener('command', (event) => {
let button = event.target;
browser.ZenWorkspaces._contextMenuId = button.closest('toolbarbutton[zen-workspace-id]').getAttribute('zen-workspace-id');
const popup = button.ownerDocument.getElementById('zenWorkspaceActionsMenu');
popup.openPopup(button, 'after_end');
});
element.appendChild(childs);
element.onclick = (async () => {
if (event.target.closest('.zen-workspace-actions')) {
return; // Ignore clicks on the actions button
}
await browser.ZenWorkspaces.changeWorkspace(workspace);
let panel = browser.document.getElementById('PanelUI-zen-workspaces');
PanelMultiView.hidePopup(panel);
browser.document.getElementById('zen-workspaces-button').removeAttribute('open');
}).bind(browser.ZenWorkspaces, workspace, browser);
return element;
};
browser.ZenWorkspaces._workspaceCache = null;
let workspaces = await browser.ZenWorkspaces._workspaces();
let activeWorkspace = workspaces.workspaces.find((workspace) => workspace.used);
currentContainer.innerHTML = '';
workspaceList.innerHTML = '';
workspaceList.parentNode.style.display = 'flex';
if (workspaces.workspaces.length - 1 <= 0) {
workspaceList.innerHTML = 'No workspaces available';
workspaceList.setAttribute('empty', 'true');
} else {
workspaceList.removeAttribute('empty');
} }
let workspaceElement = createWorkspaceElement(workspace); if (activeWorkspace) {
workspaceList.appendChild(workspaceElement); let currentWorkspace = createWorkspaceElement(activeWorkspace);
} currentContainer.appendChild(currentWorkspace);
}, }
for (let workspace of workspaces.workspaces) {
if (workspace.used) {
continue;
}
let workspaceElement = createWorkspaceElement(workspace);
workspaceList.appendChild(workspaceElement);
}
if (!ignoreStrip) {
await browser.ZenWorkspaces._expandWorkspacesStrip(browser);
}
});
}
async openWorkspacesDialog(event) { async openWorkspacesDialog(event) {
if (!this.workspaceEnabled) { if (!this.workspaceEnabled) {
return; return;
} }
let target = event.target; let target = document.getElementById('zen-workspaces-button');
let panel = document.getElementById('PanelUI-zen-workspaces'); let panel = document.getElementById('PanelUI-zen-workspaces');
await this._propagateWorkspaceData({ await this._propagateWorkspaceData({
ignoreStrip: true ignoreStrip: true
@ -365,7 +368,7 @@ var ZenWorkspaces = {
position: 'bottomright topright', position: 'bottomright topright',
triggerEvent: event, triggerEvent: event,
}).catch(console.error); }).catch(console.error);
}, }
async initializeWorkspacesButton() { async initializeWorkspacesButton() {
if (!this.workspaceEnabled) { if (!this.workspaceEnabled) {
@ -376,12 +379,12 @@ var ZenWorkspaces = {
return; return;
} }
await this._expandWorkspacesStrip(); await this._expandWorkspacesStrip();
}, }
async _expandWorkspacesStrip() { async _expandWorkspacesStrip(browser = window) {
let workspaces = await this._workspaces(); let workspaces = await browser.ZenWorkspaces._workspaces();
let workspaceList = document.getElementById('zen-workspaces-button'); let workspaceList = browser.document.getElementById('zen-workspaces-button');
const newWorkspacesButton = document.createXULElement('toolbarbutton'); const newWorkspacesButton = browser.document.createXULElement('toolbarbutton');
newWorkspacesButton.id = 'zen-workspaces-button'; newWorkspacesButton.id = 'zen-workspaces-button';
newWorkspacesButton.setAttribute('removable', 'true'); newWorkspacesButton.setAttribute('removable', 'true');
newWorkspacesButton.setAttribute('showInPrivateBrowsing', 'false'); newWorkspacesButton.setAttribute('showInPrivateBrowsing', 'false');
@ -389,7 +392,7 @@ var ZenWorkspaces = {
if (this.shouldShowIconStrip) { if (this.shouldShowIconStrip) {
for (let workspace of workspaces.workspaces) { for (let workspace of workspaces.workspaces) {
let button = document.createXULElement('toolbarbutton'); let button = browser.document.createXULElement('toolbarbutton');
button.className = 'subviewbutton'; button.className = 'subviewbutton';
button.setAttribute('tooltiptext', workspace.name); button.setAttribute('tooltiptext', workspace.name);
button.setAttribute('zen-workspace-id', workspace.uuid); button.setAttribute('zen-workspace-id', workspace.uuid);
@ -405,50 +408,50 @@ var ZenWorkspaces = {
return; return;
} }
await this.changeWorkspace(workspace); await this.changeWorkspace(workspace);
}).bind(this, workspace); }).bind(browser.ZenWorkspaces, workspace);
let icon = document.createXULElement('div'); let icon = browser.document.createXULElement('div');
icon.className = 'zen-workspace-icon'; icon.className = 'zen-workspace-icon';
icon.textContent = this.getWorkspaceIcon(workspace); icon.textContent = this.getWorkspaceIcon(workspace);
button.appendChild(icon); button.appendChild(icon);
newWorkspacesButton.appendChild(button); newWorkspacesButton.appendChild(button);
} }
// Listen for context menu events and open the all workspaces dialog // Listen for context menu events and open the all workspaces dialog
newWorkspacesButton.addEventListener('contextmenu', (event) => { newWorkspacesButton.addEventListener('contextmenu', ((event) => {
event.preventDefault(); event.preventDefault();
this.openWorkspacesDialog(event); browser.ZenWorkspaces.openWorkspacesDialog(event);
}); }).bind(this));
} }
workspaceList.after(newWorkspacesButton); workspaceList.after(newWorkspacesButton);
workspaceList.remove(); workspaceList.remove();
if (!this.shouldShowIconStrip) { if (!this.shouldShowIconStrip) {
await this._updateWorkspacesButton(); await this._updateWorkspacesButton(browser);
} }
}, }
async _updateWorkspacesButton() { async _updateWorkspacesButton(browser = window) {
let button = document.getElementById('zen-workspaces-button'); let button = browser.document.getElementById('zen-workspaces-button');
if (!button) { if (!button) {
return; return;
} }
let activeWorkspace = (await this._workspaces()).workspaces.find((workspace) => workspace.used); let activeWorkspace = (await browser.ZenWorkspaces._workspaces()).workspaces.find((workspace) => workspace.used);
if (activeWorkspace) { if (activeWorkspace) {
button.setAttribute('as-button', 'true'); button.setAttribute('as-button', 'true');
button.classList.add('toolbarbutton-1', 'zen-sidebar-action-button'); button.classList.add('toolbarbutton-1', 'zen-sidebar-action-button');
button.addEventListener('click', this.openWorkspacesDialog.bind(this)); button.addEventListener('click', browser.ZenWorkspaces.openWorkspacesDialog.bind(browser.ZenWorkspaces));
const wrapper = document.createXULElement('hbox'); const wrapper = browser.document.createXULElement('hbox');
wrapper.className = 'zen-workspace-sidebar-wrapper'; wrapper.className = 'zen-workspace-sidebar-wrapper';
const icon = document.createElement('div'); const icon = browser.document.createElement('div');
icon.className = 'zen-workspace-sidebar-icon'; icon.className = 'zen-workspace-sidebar-icon';
icon.textContent = this.getWorkspaceIcon(activeWorkspace); icon.textContent = this.getWorkspaceIcon(activeWorkspace);
// use text content instead of innerHTML to avoid XSS // use text content instead of innerHTML to avoid XSS
const name = document.createElement('div'); const name = browser.document.createElement('div');
name.className = 'zen-workspace-sidebar-name'; name.className = 'zen-workspace-sidebar-name';
name.textContent = activeWorkspace.name; name.textContent = activeWorkspace.name;
@ -462,25 +465,25 @@ var ZenWorkspaces = {
button.innerHTML = ''; button.innerHTML = '';
button.appendChild(wrapper); button.appendChild(wrapper);
} }
}, }
// Workspaces management // Workspaces management
get _workspaceCreateInput() { get _workspaceCreateInput() {
return document.getElementById('PanelUI-zen-workspaces-create-input'); return document.getElementById('PanelUI-zen-workspaces-create-input');
}, }
get _workspaceEditDialog() { get _workspaceEditDialog() {
return document.getElementById('PanelUI-zen-workspaces-edit'); return document.getElementById('PanelUI-zen-workspaces-edit');
}, }
get _workspaceEditInput() { get _workspaceEditInput() {
return document.getElementById('PanelUI-zen-workspaces-edit-input'); return document.getElementById('PanelUI-zen-workspaces-edit-input');
}, }
get _workspaceEditIconsContainer() { get _workspaceEditIconsContainer() {
return document.getElementById('PanelUI-zen-workspaces-edit-icons-container'); return document.getElementById('PanelUI-zen-workspaces-edit-icons-container');
}, }
_deleteAllTabsInWorkspace(workspaceID) { _deleteAllTabsInWorkspace(workspaceID) {
for (let tab of gBrowser.tabs) { for (let tab of gBrowser.tabs) {
@ -492,7 +495,7 @@ var ZenWorkspaces = {
}); });
} }
} }
}, }
_prepareNewWorkspace(window) { _prepareNewWorkspace(window) {
document.documentElement.setAttribute('zen-workspace-id', window.uuid); document.documentElement.setAttribute('zen-workspace-id', window.uuid);
@ -506,12 +509,12 @@ var ZenWorkspaces = {
if (tabCount === 0) { if (tabCount === 0) {
this._createNewTabForWorkspace(window); this._createNewTabForWorkspace(window);
} }
}, }
_createNewTabForWorkspace(window) { _createNewTabForWorkspace(window) {
let tab = gZenUIManager.openAndChangeToTab(Services.prefs.getStringPref('browser.startup.homepage')); let tab = gZenUIManager.openAndChangeToTab(Services.prefs.getStringPref('browser.startup.homepage'));
tab.setAttribute('zen-workspace-id', window.uuid); tab.setAttribute('zen-workspace-id', window.uuid);
}, }
async saveWorkspaceFromCreate() { async saveWorkspaceFromCreate() {
let workspaceName = this._workspaceCreateInput.value; let workspaceName = this._workspaceCreateInput.value;
@ -525,7 +528,7 @@ var ZenWorkspaces = {
document.getElementById('PanelUI-zen-workspaces').hidePopup(true); document.getElementById('PanelUI-zen-workspaces').hidePopup(true);
await this._updateWorkspacesButton(); await this._updateWorkspacesButton();
await this._propagateWorkspaceData(); await this._propagateWorkspaceData();
}, }
async saveWorkspaceFromEdit() { async saveWorkspaceFromEdit() {
let workspaceUuid = this._workspaceEditDialog.getAttribute('data-workspace-uuid'); let workspaceUuid = this._workspaceEditDialog.getAttribute('data-workspace-uuid');
@ -541,10 +544,9 @@ var ZenWorkspaces = {
workspaceData.name = workspaceName; workspaceData.name = workspaceName;
workspaceData.icon = icon?.label; workspaceData.icon = icon?.label;
await this.saveWorkspace(workspaceData); await this.saveWorkspace(workspaceData);
await this._updateWorkspacesButton();
await this._propagateWorkspaceData(); await this._propagateWorkspaceData();
this.closeWorkspacesSubView(); this.closeWorkspacesSubView();
}, }
onWorkspaceCreationNameChange(event) { onWorkspaceCreationNameChange(event) {
let button = document.getElementById('PanelUI-zen-workspaces-create-save'); let button = document.getElementById('PanelUI-zen-workspaces-create-save');
@ -553,7 +555,7 @@ var ZenWorkspaces = {
return; return;
} }
button.removeAttribute('disabled'); button.removeAttribute('disabled');
}, }
onWorkspaceEditChange() { onWorkspaceEditChange() {
let button = document.getElementById('PanelUI-zen-workspaces-edit-save'); let button = document.getElementById('PanelUI-zen-workspaces-edit-save');
@ -567,68 +569,64 @@ var ZenWorkspaces = {
return; return;
} }
button.removeAttribute('disabled'); button.removeAttribute('disabled');
}, }
get _shouldAllowPinTab() { get _shouldAllowPinTab() {
return Services.prefs.getBoolPref('zen.workspaces.individual-pinned-tabs'); return Services.prefs.getBoolPref('zen.workspaces.individual-pinned-tabs');
}, }
get tabContainer() {
delete this.tabContainer;
return (this.tabContainer = document.getElementById("tabbrowser-tabs"));
},
async changeWorkspace(window, onInit = false) { async changeWorkspace(window, onInit = false) {
if (!this.workspaceEnabled) { if (!this.workspaceEnabled) {
return; return;
} }
this.tabContainer._invalidateCachedTabs();
const shouldAllowPinnedTabs = this._shouldAllowPinTab;
let firstTab = undefined;
let workspaces = await this._workspaces(); let workspaces = await this._workspaces();
for (let workspace of workspaces.workspaces) { for (let workspace of workspaces.workspaces) {
workspace.used = workspace.uuid === window.uuid; workspace.used = workspace.uuid === window.uuid;
} }
await this.unsafeSaveWorkspaces(workspaces); await this.unsafeSaveWorkspaces(workspaces);
console.info('ZenWorkspaces: Changing workspace to', window.uuid); const shouldAllowPinnedTabs = this._shouldAllowPinTab;
for (let tab of gBrowser.tabs) { await this.foreachWindowAsActive(async (browser) => {
if ((tab.getAttribute('zen-workspace-id') === window.uuid && !(tab.pinned && !shouldAllowPinnedTabs)) || !tab.hasAttribute('zen-workspace-id')) { browser.ZenWorkspaces.tabContainer._invalidateCachedTabs();
if (!firstTab) { let firstTab = undefined;
firstTab = tab; console.info('ZenWorkspaces: Changing workspace to', window.uuid);
} else if (gBrowser.selectedTab === tab) { for (let tab of browser.gBrowser.tabs) {
// If the selected tab is already in the workspace, we don't want to change it if ((tab.getAttribute('zen-workspace-id') === window.uuid && !(tab.pinned && !shouldAllowPinnedTabs)) || !tab.hasAttribute('zen-workspace-id')) {
firstTab = null; // note: Do not add "undefined" here, a new tab would be created if (!firstTab) {
} firstTab = tab;
gBrowser.showTab(tab); } else if (browser.gBrowser.selectedTab === tab) {
if (!tab.hasAttribute('zen-workspace-id')) { // If the selected tab is already in the workspace, we don't want to change it
// We add the id to those tabs that got inserted before we initialize the workspaces firstTab = null; // note: Do not add "undefined" here, a new tab would be created
// example use case: opening a link from an external app }
tab.setAttribute('zen-workspace-id', window.uuid); browser.gBrowser.showTab(tab);
if (!tab.hasAttribute('zen-workspace-id')) {
// We add the id to those tabs that got inserted before we initialize the workspaces
// example use case: opening a link from an external app
tab.setAttribute('zen-workspace-id', window.uuid);
}
} }
} }
} if (firstTab) {
if (firstTab) { browser.gBrowser.selectedTab = browser.ZenWorkspaces._lastSelectedWorkspaceTabs[window.uuid] ?? firstTab;
gBrowser.selectedTab = this._lastSelectedWorkspaceTabs[window.uuid] ?? firstTab;
}
if (typeof firstTab === 'undefined' && !onInit) {
this._createNewTabForWorkspace(window);
}
for (let tab of gBrowser.tabs) {
if (tab.getAttribute('zen-workspace-id') !== window.uuid) {
// FOR UNLOADING TABS:
// gBrowser.discardBrowser(tab, true);
gBrowser.hideTab(tab, undefined, shouldAllowPinnedTabs);
} }
} if (typeof firstTab === 'undefined' && !onInit) {
this.tabContainer._invalidateCachedTabs(); browser.ZenWorkspaces._createNewTabForWorkspace(window);
document.documentElement.setAttribute('zen-workspace-id', window.uuid); }
await this.saveWorkspaces(); for (let tab of browser.gBrowser.tabs) {
await this._updateWorkspacesButton(); if (tab.getAttribute('zen-workspace-id') !== window.uuid) {
await this._propagateWorkspaceData(); // FOR UNLOADING TABS:
await this._updateWorkspacesChangeContextMenu(); // gBrowser.discardBrowser(tab, true);
browser.gBrowser.hideTab(tab, undefined, shouldAllowPinnedTabs);
}
}
browser.ZenWorkspaces.tabContainer._invalidateCachedTabs();
browser.document.documentElement.setAttribute('zen-workspace-id', window.uuid);
await browser.ZenWorkspaces._updateWorkspacesChangeContextMenu();
document.getElementById('tabbrowser-tabs')._positionPinnedTabs(); browser.document.getElementById('tabbrowser-tabs')._positionPinnedTabs();
}, });
await this.saveWorkspaces();
await this._propagateWorkspaceData();
}
async _updateWorkspacesChangeContextMenu() { async _updateWorkspacesChangeContextMenu() {
const workspaces = await this._workspaces(); const workspaces = await this._workspaces();
@ -650,7 +648,7 @@ var ZenWorkspaces = {
menuPopup.appendChild(menuItem); menuPopup.appendChild(menuItem);
} }
}, }
_createWorkspaceData(name, isDefault, icon) { _createWorkspaceData(name, isDefault, icon) {
let window = { let window = {
@ -662,7 +660,7 @@ var ZenWorkspaces = {
}; };
this._prepareNewWorkspace(window); this._prepareNewWorkspace(window);
return window; return window;
}, }
async createAndSaveWorkspace(name = 'New Workspace', isDefault = false, icon = undefined) { async createAndSaveWorkspace(name = 'New Workspace', isDefault = false, icon = undefined) {
if (!this.workspaceEnabled) { if (!this.workspaceEnabled) {
@ -671,7 +669,7 @@ var ZenWorkspaces = {
let workspaceData = this._createWorkspaceData(name, isDefault, icon); let workspaceData = this._createWorkspaceData(name, isDefault, icon);
await this.saveWorkspace(workspaceData); await this.saveWorkspace(workspaceData);
await this.changeWorkspace(workspaceData); await this.changeWorkspace(workspaceData);
}, }
async onTabBrowserInserted(event) { async onTabBrowserInserted(event) {
let tab = event.originalTarget; let tab = event.originalTarget;
@ -684,7 +682,7 @@ var ZenWorkspaces = {
return; return;
} }
tab.setAttribute('zen-workspace-id', activeWorkspace.uuid); tab.setAttribute('zen-workspace-id', activeWorkspace.uuid);
}, }
async onLocationChange(browser) { async onLocationChange(browser) {
let tab = gBrowser.getTabForBrowser(browser); let tab = gBrowser.getTabForBrowser(browser);
@ -699,11 +697,11 @@ var ZenWorkspaces = {
workspaceID = activeWorkspace.uuid; workspaceID = activeWorkspace.uuid;
} }
this._lastSelectedWorkspaceTabs[workspaceID] = tab; this._lastSelectedWorkspaceTabs[workspaceID] = tab;
}, }
// Context menu management // Context menu management
_contextMenuId: null, _contextMenuId = null
async updateContextMenu(_) { async updateContextMenu(_) {
console.assert(this._contextMenuId, 'No context menu ID set'); console.assert(this._contextMenuId, 'No context menu ID set');
document document
@ -737,7 +735,7 @@ var ZenWorkspaces = {
} else { } else {
openInContainerMenuItem.setAttribute('hidden', 'true'); openInContainerMenuItem.setAttribute('hidden', 'true');
} }
}, }
async contextChangeContainerTab(event) { async contextChangeContainerTab(event) {
let workspaces = await this._workspaces(); let workspaces = await this._workspaces();
@ -746,7 +744,7 @@ var ZenWorkspaces = {
workspace.containerTabId = userContextId; workspace.containerTabId = userContextId;
await this.saveWorkspace(workspace); await this.saveWorkspace(workspace);
await this._propagateWorkspaceData(); await this._propagateWorkspaceData();
}, }
onContextMenuClose() { onContextMenuClose() {
let target = document.querySelector( let target = document.querySelector(
@ -756,7 +754,7 @@ var ZenWorkspaces = {
target.removeAttribute('active'); target.removeAttribute('active');
} }
this._contextMenuId = null; this._contextMenuId = null;
}, }
async setDefaultWorkspace() { async setDefaultWorkspace() {
let workspaces = await this._workspaces(); let workspaces = await this._workspaces();
@ -765,25 +763,25 @@ var ZenWorkspaces = {
} }
await this.unsafeSaveWorkspaces(workspaces); await this.unsafeSaveWorkspaces(workspaces);
await this._propagateWorkspaceData(); await this._propagateWorkspaceData();
}, }
async openWorkspace() { async openWorkspace() {
let workspaces = await this._workspaces(); let workspaces = await this._workspaces();
let workspace = workspaces.workspaces.find((workspace) => workspace.uuid === this._contextMenuId); let workspace = workspaces.workspaces.find((workspace) => workspace.uuid === this._contextMenuId);
await this.changeWorkspace(workspace); await this.changeWorkspace(workspace);
}, }
async contextDelete(event) { async contextDelete(event) {
this.__contextIsDelete = true; this.__contextIsDelete = true;
event.stopPropagation(); event.stopPropagation();
await this.removeWorkspace(this._contextMenuId); await this.removeWorkspace(this._contextMenuId);
this.__contextIsDelete = false; this.__contextIsDelete = false;
}, }
async contextEdit(event) { async contextEdit(event) {
event.stopPropagation(); event.stopPropagation();
await this.openEditDialog(this._contextMenuId); await this.openEditDialog(this._contextMenuId);
}, }
async changeWorkspaceShortcut(offset = 1) { async changeWorkspaceShortcut(offset = 1) {
// Cycle through workspaces // Cycle through workspaces
@ -793,7 +791,7 @@ var ZenWorkspaces = {
// note: offset can be negative // note: offset can be negative
let nextWorkspace = workspaces.workspaces[(workspaceIndex + offset + workspaces.workspaces.length) % workspaces.workspaces.length]; let nextWorkspace = workspaces.workspaces[(workspaceIndex + offset + workspaces.workspaces.length) % workspaces.workspaces.length];
await this.changeWorkspace(nextWorkspace); await this.changeWorkspace(nextWorkspace);
}, }
_initializeWorkspaceTabContextMenus() { _initializeWorkspaceTabContextMenus() {
const menu = document.createXULElement('menu'); const menu = document.createXULElement('menu');
@ -807,7 +805,7 @@ var ZenWorkspaces = {
menu.appendChild(menuPopup); menu.appendChild(menuPopup);
document.getElementById('context_closeDuplicateTabs').after(menu); document.getElementById('context_closeDuplicateTabs').after(menu);
}, }
async changeTabWorkspace(workspaceID) { async changeTabWorkspace(workspaceID) {
const tabs = TabContextMenu.contextTab.multiselected ? gBrowser.selectedTabs : [TabContextMenu.contextTab]; const tabs = TabContextMenu.contextTab.multiselected ? gBrowser.selectedTabs : [TabContextMenu.contextTab];
@ -822,7 +820,7 @@ var ZenWorkspaces = {
} }
const workspaces = await this._workspaces(); const workspaces = await this._workspaces();
await this.changeWorkspace(workspaces.workspaces.find((workspace) => workspace.uuid === workspaceID)); await this.changeWorkspace(workspaces.workspaces.find((workspace) => workspace.uuid === workspaceID));
}, }
// Tab browser utilities // Tab browser utilities
createContainerTabMenu(event) { createContainerTabMenu(event) {
@ -834,7 +832,7 @@ var ZenWorkspaces = {
excludeUserContextId: containerTabId, excludeUserContextId: containerTabId,
showDefaultTab: true, showDefaultTab: true,
}); });
}, }
getContextIdIfNeeded(userContextId) { getContextIdIfNeeded(userContextId) {
const activeWorkspace = this.getActiveWorkspaceFromCache(); const activeWorkspace = this.getActiveWorkspaceFromCache();
@ -844,7 +842,7 @@ var ZenWorkspaces = {
return [userContextId, false]; return [userContextId, false];
} }
return [activeWorkspaceUserContextId, true]; return [activeWorkspaceUserContextId, true];
}, }
async shortcutSwitchTo(index) { async shortcutSwitchTo(index) {
const workspaces = await this._workspaces(); const workspaces = await this._workspaces();